import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Reflector } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import type { AuthUser } from '@vihar/shared';
import { IS_PUBLIC_KEY, IS_INTERNAL_KEY } from '../decorators/auth.decorators';

export const COOKIE_NAME = 'vihar_session';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(
    private readonly jwt: JwtService,
    private readonly config: ConfigService,
    private readonly reflector: Reflector,
  ) {}

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

    // Internal routes use a different guard; let them pass here so InternalGuard handles them
    const isInternal = this.reflector.getAllAndOverride<boolean>(IS_INTERNAL_KEY, [
      ctx.getHandler(),
      ctx.getClass(),
    ]);
    if (isInternal) return true;

    const req = ctx.switchToHttp().getRequest<Request>();
    const token = this.extractToken(req);
    if (!token) throw new UnauthorizedException('Not authenticated');

    try {
      const secret = this.config.get<string>('JWT_SECRET');
      if (!secret) throw new Error('JWT_SECRET not configured');
      const payload = this.jwt.verify<AuthUser & { iat: number; exp: number }>(token, { secret });
      // Strip JWT-specific claims from user object
      const { iat: _iat, exp: _exp, ...user } = payload;
      (req as Request & { user: AuthUser }).user = user;
      return true;
    } catch {
      throw new UnauthorizedException('Invalid or expired session');
    }
  }

  private extractToken(req: Request): string | null {
    const cookies = (req as Request & { cookies?: Record<string, string> }).cookies;
    if (cookies && cookies[COOKIE_NAME]) return cookies[COOKIE_NAME];

    // Fallback: Bearer token (for local tooling only — disabled in production)
    if (process.env.NODE_ENV !== 'production') {
      const auth = req.headers.authorization;
      if (auth && auth.startsWith('Bearer ')) return auth.slice(7);
    }
    return null;
  }
}
