import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
  Logger,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthGuard } from "@nestjs/passport";
import { ExtractJwt } from "passport-jwt";

import type { Request } from "express";

import { TokenBlacklistService } from "../../../infra/token/token-blacklist.service";
import { IS_PUBLIC_KEY } from "../../../decorators/public.decorator";
import { getErrorMessage } from "@bff/core/utils/error.util";

type CookieValue = string | undefined;
type RequestBase = Omit<Request, "cookies" | "route">;
type RequestWithCookies = RequestBase & {
  cookies?: Record<string, CookieValue>;
};
type RequestWithRoute = RequestWithCookies & {
  method: string;
  url: string;
  route?: { path?: string };
};

const headerExtractor = ExtractJwt.fromAuthHeaderAsBearerToken();
const extractTokenFromRequest = (request: RequestWithCookies): string | undefined => {
  const headerToken = headerExtractor(request);
  if (headerToken) {
    return headerToken;
  }
  const cookieToken = request.cookies?.access_token;
  return typeof cookieToken === "string" && cookieToken.length > 0 ? cookieToken : undefined;
};

@Injectable()
export class GlobalAuthGuard extends AuthGuard("jwt") implements CanActivate {
  private readonly logger = new Logger(GlobalAuthGuard.name);

  constructor(
    private reflector: Reflector,
    private tokenBlacklistService: TokenBlacklistService
  ) {
    super();
  }

  override async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = this.getRequest(context);
    const route = `${request.method} ${request.url}`;
    const isLogoutRoute = this.isLogoutRoute(request);

    // Check if the route is marked as public
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      this.logger.debug(`Public route accessed: ${route}`);
      return true;
    }

    try {
      // First, run the standard JWT authentication
      const canActivate = await super.canActivate(context);
      if (!canActivate) {
        if (isLogoutRoute) {
          this.logger.debug(`Allowing logout request without active session: ${route}`);
          return true;
        }
        this.logger.warn(`JWT authentication failed for route: ${route}`);
        return false;
      }

      // Then check token blacklist
      const token = extractTokenFromRequest(request);

      if (token) {
        const isBlacklisted = await this.tokenBlacklistService.isTokenBlacklisted(token);
        if (isBlacklisted) {
          this.logger.warn(`Blacklisted token attempted access to: ${route}`);
          throw new UnauthorizedException("Token has been revoked");
        }
      }

      this.logger.debug(`Authenticated access to: ${route}`);
      return true;
    } catch (error) {
      if (error instanceof UnauthorizedException) {
        if (isLogoutRoute) {
          this.logger.debug(`Allowing logout request with expired or invalid token: ${route}`);
          return true;
        }
        const token = extractTokenFromRequest(request);
        const log =
          typeof token === "string"
            ? () => this.logger.warn(`Unauthorized access attempt to ${route}`)
            : () => this.logger.debug(`Unauthenticated request blocked for ${route}`);
        log();
      } else {
        this.logger.error(`Authentication error for route ${route}: ${getErrorMessage(error)}`);
      }
      throw error;
    }
  }
  override getRequest(context: ExecutionContext): RequestWithRoute {
    const rawRequest = context.switchToHttp().getRequest<unknown>();
    if (!this.isRequestWithRoute(rawRequest)) {
      this.logger.error("Unable to determine HTTP request in auth guard");
      throw new UnauthorizedException("Invalid request context");
    }
    return rawRequest;
  }

  private isRequestWithRoute(value: unknown): value is RequestWithRoute {
    if (!value || typeof value !== "object") {
      return false;
    }
    const candidate = value as Partial<RequestWithRoute>;
    return (
      typeof candidate.method === "string" &&
      typeof candidate.url === "string" &&
      (!candidate.route || typeof candidate.route === "object")
    );
  }

  private isLogoutRoute(request: RequestWithRoute): boolean {
    if (!request || typeof request.method !== "string" || typeof request.url !== "string") {
      return false;
    }

    if (request.method.toUpperCase() !== "POST") {
      return false;
    }

    const routePath = request.route?.path;
    if (routePath && this.isLogoutPath(routePath)) {
      return true;
    }

    const normalizedUrl = request.url.split("?")[0] ?? "";
    return this.isLogoutPath(normalizedUrl);
  }

  private isLogoutPath(path: string): boolean {
    if (typeof path !== "string" || path.length === 0) {
      return false;
    }

    const normalized = path.endsWith("/") ? path.slice(0, -1) : path;
    return normalized === "/auth/logout" || normalized === "/api/auth/logout";
  }
}
