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 workspace real.
// CODING-RULES PGD-AUTHZ-002: workspaceId sempre do JWT, nunca de body/param.
// Ordem NestJS: middleware CLS (workspace default) → este guard (workspace real).

@Injectable()
export class JwtAuthGuard implements CanActivate {
  private readonly secret: Uint8Array;

  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 }));
  }

  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;

      // Sobrescreve CLS com workspace real do JWT (corre depois do middleware).
      const workspaceId = payload.workspace_id;
      this.cls.set('workspaceId', workspaceId);
      this.cls.set('userId', payload.sub);

      const dbUrl =
        process.env['DATABASE_URL'] ??
        `postgresql://sar:sar_dev_password@localhost:5432/sar_workspace_${workspaceId}`;
      this.cls.set('prisma', this.pool.getOrCreate(workspaceId, dbUrl));

      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;
  }
}
