import { Injectable, NestMiddleware, UnauthorizedException, ForbiddenException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { TenantContextService } from './tenant-context.service.js';
import { TenantContext, TenantRequest, TenantStatus } from './tenant.types.js';

/**
 * 租户查找器接口
 * 各服务需要实现此接口来提供租户数据
 */
export interface ITenantFinder {
  findById(id: string): Promise<TenantContext | null>;
  findBySlug?(slug: string): Promise<TenantContext | null>;
}

/**
 * 租户查找器注入令牌
 */
export const TENANT_FINDER = Symbol('TENANT_FINDER');

/**
 * 租户上下文中间件配置
 */
export interface TenantMiddlewareOptions {
  /**
   * 是否允许使用默认租户 (用于向后兼容)
   */
  allowDefaultTenant?: boolean;
  /**
   * 默认租户 ID
   */
  defaultTenantId?: string;
  /**
   * 排除的路径 (不需要租户上下文)
   */
  excludePaths?: string[];
}

/**
 * 租户上下文中间件
 * 从请求中提取租户标识并建立租户上下文
 */
@Injectable()
export class TenantContextMiddleware implements NestMiddleware {
  constructor(
    private readonly tenantContext: TenantContextService,
    private readonly tenantFinder: ITenantFinder,
    private readonly options: TenantMiddlewareOptions = {},
  ) {}

  async use(req: Request, _res: Response, next: NextFunction): Promise<void> {
    // 检查是否是排除的路径
    if (this.isExcludedPath(req.path)) {
      return next();
    }

    // 提取租户标识
    const tenantId = await this.extractTenantId(req);

    if (!tenantId) {
      throw new UnauthorizedException('Tenant identification required. Provide x-tenant-id header.');
    }

    // 加载租户数据
    const tenant = await this.tenantFinder.findById(tenantId);

    if (!tenant) {
      throw new UnauthorizedException('Invalid tenant');
    }

    // 验证租户状态
    if (tenant.status !== TenantStatus.ACTIVE) {
      throw new ForbiddenException(`Tenant is ${tenant.status.toLowerCase()}`);
    }

    // 设置请求属性
    const tenantRequest = req as unknown as TenantRequest;
    tenantRequest.tenant = tenant;
    tenantRequest.tenantId = tenant.id;

    // 提取用户 ID
    const userId = req.headers['x-user-id'] as string;

    // 在租户上下文中执行后续中间件和处理程序
    this.tenantContext.run(tenant, () => {
      if (userId) {
        this.tenantContext.setCurrentUserId(userId);
      }
      next();
    }, userId);
  }

  /**
   * 从请求中提取租户标识
   */
  private async extractTenantId(req: Request): Promise<string | null> {
    // 优先级 1: x-tenant-id 请求头
    const headerTenantId = req.headers['x-tenant-id'] as string;
    if (headerTenantId) {
      return headerTenantId;
    }

    // 优先级 2: 子域名
    const host = req.headers.host || '';
    const subdomain = this.extractSubdomain(host);
    if (subdomain && subdomain !== 'www' && subdomain !== 'api' && this.tenantFinder.findBySlug) {
      const tenant = await this.tenantFinder.findBySlug(subdomain);
      if (tenant) {
        return tenant.id;
      }
    }

    // 优先级 3: 查询参数 (用于测试)
    const queryTenantId = req.query['tenant_id'] as string;
    if (queryTenantId) {
      return queryTenantId;
    }

    // 优先级 4: 默认租户 (向后兼容)
    if (this.options.allowDefaultTenant && this.options.defaultTenantId) {
      return this.options.defaultTenantId;
    }

    return null;
  }

  /**
   * 从主机名提取子域名
   */
  private extractSubdomain(host: string): string | null {
    // 移除端口号
    const hostname = host.split(':')[0];
    const parts = hostname.split('.');

    // 至少需要三个部分才有子域名 (subdomain.domain.tld)
    if (parts.length >= 3) {
      return parts[0];
    }

    return null;
  }

  /**
   * 检查路径是否在排除列表中
   */
  private isExcludedPath(path: string): boolean {
    const excludePaths = this.options.excludePaths || [];
    return excludePaths.some((excluded) => {
      if (excluded.endsWith('*')) {
        return path.startsWith(excluded.slice(0, -1));
      }
      return path === excluded;
    });
  }
}

/**
 * 创建租户中间件工厂函数
 * 用于在没有 DI 容器的情况下创建中间件
 */
export function createTenantMiddleware(
  tenantContext: TenantContextService,
  tenantFinder: ITenantFinder,
  options?: TenantMiddlewareOptions,
): TenantContextMiddleware {
  return new TenantContextMiddleware(tenantContext, tenantFinder, options);
}
