import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ClsService } from 'nestjs-cls';
import { randomUUID } from 'node:crypto';
import {
  CLS_TENANT_ID,
  CLS_IP_ADDRESS,
  CLS_USER_AGENT,
  CLS_REQUEST_ID,
} from '../cls/cls.constants';

function setRequestContext(cls: ClsService, req: Request): void {
  // Trust the first X-Forwarded-For when present (dev server is behind a
  // reverse proxy); otherwise fall back to the socket address.
  const fwd = (req.headers['x-forwarded-for'] as string | undefined)?.split(',')[0]?.trim();
  const ip = fwd || req.socket?.remoteAddress || null;
  const ua = req.headers['user-agent'] || null;
  const rid = (req.headers['x-request-id'] as string | undefined) || randomUUID();

  if (ip) cls.set(CLS_IP_ADDRESS, ip.slice(0, 45));
  if (ua) cls.set(CLS_USER_AGENT, ua.slice(0, 500));
  cls.set(CLS_REQUEST_ID, rid.slice(0, 64));
}

/**
 * TenantMiddleware — resolves tenant_id from subdomain and stores in CLS.
 *
 * Per CLAUDE.md:
 * - tenant_id resolved automatically via TenantMiddleware (subdomain in prod)
 * - Stored in request-scoped async context via ClsService (nestjs-cls)
 * - No setTenantId() calls — context propagation is fully automatic
 *
 * Also captures ip/user-agent/request-id for audit logging.
 *
 * TODO: Look up tenant_id (UUID) from DB by subdomain once traffic grows.
 */
@Injectable()
export class TenantMiddleware implements NestMiddleware {
  constructor(private readonly cls: ClsService) {}

  use(req: Request, _res: Response, next: NextFunction): void {
    const host = req.headers.host || '';
    const subdomain = this.extractSubdomain(host);

    if (!subdomain) {
      throw new UnauthorizedException('Tenant could not be resolved from host');
    }

    this.cls.set(CLS_TENANT_ID, subdomain);
    setRequestContext(this.cls, req);

    next();
  }

  private extractSubdomain(host: string): string | null {
    const hostname = host.split(':')[0];

    if (hostname === 'localhost' || hostname === '127.0.0.1') {
      return null;
    }

    const parts = hostname.split('.');
    if (parts.length >= 3) {
      return parts[0];
    }

    return null;
  }
}

/**
 * DevTenantMiddleware — local development fallback.
 * Uses X-Tenant-Id header and stores in CLS.
 */
@Injectable()
export class DevTenantMiddleware implements NestMiddleware {
  constructor(private readonly cls: ClsService) {}

  use(req: Request, _res: Response, next: NextFunction): void {
    const tenantId = req.headers['x-tenant-id'] as string;

    if (!tenantId) {
      throw new UnauthorizedException('X-Tenant-Id header is required in development mode');
    }

    this.cls.set(CLS_TENANT_ID, tenantId);
    setRequestContext(this.cls, req);

    next();
  }
}
