import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Reflector } from '@nestjs/core';
import { ClsService } from 'nestjs-cls';
import { jwtVerify } from 'jose';
import type { Request } from 'express';
import type { Env } from '../config/env.schema';
import type { WorkspaceClsStore } from '../workspace/workspace.types';
import { WorkspacePrismaPool } from '../workspace/workspace-prisma-pool.service';
import type { JwtPayload } from './jwt.types';
import { IS_PUBLIC_KEY } from './public.decorator';

// Guard global (APP_GUARD). Valida Bearer HS256 e atualiza CLS com idEmpresa real.
// CODING-RULES PGD-AUTHZ-002: idEmpresa sempre do JWT, nunca de body/param.
// Ordem NestJS: middleware CLS (idEmpresa default) → este guard (idEmpresa real).
// ADR 0006 revogado: workspace_id → id_empresa; URL inclui ?schema=sar

@Injectable()
export class JwtAuthGuard implements CanActivate {
  private readonly secret: Uint8Array;
  private readonly isProd: boolean;
  private readonly devRepCode: string;
  private readonly devEmpresaId: number;

  constructor(
    private readonly reflector: Reflector,
    private readonly cls: ClsService<WorkspaceClsStore>,
    private readonly pool: WorkspacePrismaPool,
    config: ConfigService<Env, true>,
  ) {
    this.secret = new TextEncoder().encode(config.get('MASTER_LOGIN_JWT_SECRET', { infer: true }));
    this.isProd = config.get('NODE_ENV', { infer: true }) === 'production';
    this.devRepCode = String(config.get('DEV_REP_CODE', { infer: true }));
    this.devEmpresaId = config.get('DEV_EMPRESA_ID', { infer: true });
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    if (this.isPublic(context)) return true;

    const req = context.switchToHttp().getRequest<Request>();
    const token = this.extractBearer(req);

    if (!token) {
      throw new UnauthorizedException('token ausente');
    }

    try {
      const { payload } = await jwtVerify<JwtPayload>(token, this.secret, {
        algorithms: ['HS256'],
      });

      (req as Request & { user: JwtPayload }).user = payload as JwtPayload;

      // Em dev: usa sub/id_empresa do próprio JWT (emitido pelo /auth/dev/token,
      // que permite logar como Pavei/Sidnei/Lucas) e cai para DEV_REP_CODE /
      // DEV_EMPRESA_ID apenas se o token não trouxer os campos.
      // Em prod: usa os valores reais do JWT do master-login.
      const idEmpresa = payload.id_empresa ?? (this.isProd ? undefined : this.devEmpresaId);
      const userId = payload.sub ?? (this.isProd ? undefined : this.devRepCode);
      if (idEmpresa == null || userId == null) {
        throw new UnauthorizedException('token sem sub/id_empresa');
      }
      this.cls.set('idEmpresa', idEmpresa);
      this.cls.set('userId', userId);
      this.cls.set('role', payload.role);

      const baseUrl = process.env['DATABASE_URL'];
      if (!baseUrl) throw new Error('DATABASE_URL não configurada');
      this.cls.set('prisma', this.pool.getOrCreate(idEmpresa, baseUrl));

      return true;
    } catch {
      throw new UnauthorizedException('token inválido ou expirado');
    }
  }

  private isPublic(ctx: ExecutionContext): boolean {
    return (
      this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
        ctx.getHandler(),
        ctx.getClass(),
      ]) === true
    );
  }

  private extractBearer(req: Request): string | undefined {
    const auth = req.headers['authorization'];
    if (typeof auth === 'string' && auth.startsWith('Bearer ')) {
      return auth.slice(7);
    }
    return undefined;
  }
}
