import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
  Logger,
} from "@nestjs/common";
import { AuthService } from "../auth.service";
import type { AuthUser } from "@mosaic/shared";
import type { MaybeAuthenticatedRequest } from "../types/better-auth-request.interface";

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

  constructor(private readonly authService: AuthService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<MaybeAuthenticatedRequest>();

    // Try to get token from either cookie (preferred) or Authorization header
    const token = this.extractToken(request);

    if (!token) {
      throw new UnauthorizedException("No authentication token provided");
    }

    try {
      const sessionData = await this.authService.verifySession(token);

      if (!sessionData) {
        throw new UnauthorizedException("Invalid or expired session");
      }

      // Attach user and session to request
      const user = sessionData.user;
      // Validate user has required fields
      if (typeof user !== "object" || !("id" in user) || !("email" in user) || !("name" in user)) {
        throw new UnauthorizedException("Invalid user data in session");
      }
      request.user = user as unknown as AuthUser;
      request.session = sessionData.session;

      return true;
    } catch (error) {
      if (error instanceof UnauthorizedException) {
        throw error;
      }
      // Infrastructure errors (DB down, connection refused, timeouts) must propagate
      // as 500/503 via GlobalExceptionFilter — never mask as 401
      throw error;
    }
  }

  /**
   * Extract token from cookie (preferred) or Authorization header
   */
  private extractToken(request: MaybeAuthenticatedRequest): string | undefined {
    // Try cookie first (BetterAuth default)
    const cookieToken = this.extractTokenFromCookie(request);
    if (cookieToken) {
      return cookieToken;
    }

    // Fallback to Authorization header for API clients
    return this.extractTokenFromHeader(request);
  }

  /**
   * Extract token from cookie.
   * BetterAuth may prefix the cookie name with "__Secure-" when running on HTTPS.
   */
  private extractTokenFromCookie(request: MaybeAuthenticatedRequest): string | undefined {
    // Express types `cookies` as `any`; cast to a known shape for type safety.
    const cookies = request.cookies as Record<string, string> | undefined;
    if (!cookies) {
      return undefined;
    }

    // BetterAuth default cookie name is "better-auth.session_token"
    // When Secure cookies are enabled, BetterAuth prefixes with "__Secure-".
    const candidates = [
      "__Secure-better-auth.session_token",
      "better-auth.session_token",
      "__Host-better-auth.session_token",
    ] as const;

    for (const name of candidates) {
      const token = cookies[name];
      if (token) {
        this.logger.debug(`Session cookie found: ${name}`);
        return token;
      }
    }

    return undefined;
  }

  /**
   * Extract token from Authorization header (Bearer token)
   */
  private extractTokenFromHeader(request: MaybeAuthenticatedRequest): string | undefined {
    const authHeader = request.headers.authorization;
    if (typeof authHeader !== "string") {
      return undefined;
    }

    const parts = authHeader.split(" ");
    const [type, token] = parts;

    return type === "Bearer" ? token : undefined;
  }
}
