import {
  Controller,
  All,
  Req,
  Res,
  Get,
  Header,
  UseGuards,
  Request,
  Logger,
  HttpException,
  HttpStatus,
  UnauthorizedException,
} from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";
import type { Request as ExpressRequest, Response as ExpressResponse } from "express";
import type { AuthUser, AuthSession, AuthConfigResponse } from "@mosaic/shared";
import { AuthService } from "./auth.service";
import { AuthGuard } from "./guards/auth.guard";
import { CurrentUser } from "./decorators/current-user.decorator";
import { SkipCsrf } from "../common/decorators/skip-csrf.decorator";
import type { AuthenticatedRequest } from "./types/better-auth-request.interface";

@Controller("auth")
export class AuthController {
  private readonly logger = new Logger(AuthController.name);

  constructor(private readonly authService: AuthService) {}

  /**
   * Get current session
   * Returns user and session data for authenticated user
   */
  @Get("session")
  @UseGuards(AuthGuard)
  getSession(@Request() req: AuthenticatedRequest): AuthSession {
    // Defense-in-depth: AuthGuard should guarantee these, but if someone adds
    // a route with AuthenticatedRequest and forgets @UseGuards(AuthGuard),
    // TypeScript types won't help at runtime.
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    if (!req.user || !req.session) {
      throw new UnauthorizedException("Missing authentication context");
    }

    return {
      user: req.user,
      session: {
        id: req.session.id,
        token: req.session.token,
        expiresAt: req.session.expiresAt,
      },
    };
  }

  /**
   * Get current user profile
   * Returns basic user information
   */
  @Get("profile")
  @UseGuards(AuthGuard)
  getProfile(@CurrentUser() user: AuthUser): AuthUser {
    // Return only defined properties to maintain type safety
    const profile: AuthUser = {
      id: user.id,
      email: user.email,
      name: user.name,
    };

    if (user.image !== undefined) {
      profile.image = user.image;
    }
    if (user.emailVerified !== undefined) {
      profile.emailVerified = user.emailVerified;
    }

    // Workspace context is served by GET /api/workspaces, not the auth profile.
    // The deprecated workspaceId/currentWorkspaceId/workspaceRole fields on
    // AuthUser are never populated by BetterAuth and are omitted here.

    return profile;
  }

  /**
   * Get available authentication providers.
   * Public endpoint (no auth guard) so the frontend can discover login options
   * before the user is authenticated.
   */
  @Get("config")
  @Header("Cache-Control", "public, max-age=300")
  async getConfig(): Promise<AuthConfigResponse> {
    return this.authService.getAuthConfig();
  }

  /**
   * Handle all other auth routes (sign-in, sign-up, sign-out, etc.)
   * Delegates to BetterAuth
   *
   * Rate limit: "strict" tier (10 req/min) - More restrictive than normal routes
   * to prevent brute-force attacks on auth endpoints
   *
   * Security note: This catch-all route bypasses standard guards that other routes have.
   * Rate limiting and logging are applied to mitigate abuse (SEC-API-10).
   */
  @All("*")
  // BetterAuth handles CSRF internally (Fetch Metadata + SameSite=Lax cookies).
  // @SkipCsrf avoids double-protection conflicts.
  // See: https://www.better-auth.com/docs/reference/security
  @SkipCsrf()
  @Throttle({ default: { ttl: 60_000, limit: 5 } })
  async handleAuth(@Req() req: ExpressRequest, @Res() res: ExpressResponse): Promise<void> {
    // Extract client IP for logging
    const clientIp = this.getClientIp(req);

    // Log auth catch-all hits for monitoring and debugging
    this.logger.debug(`Auth catch-all: ${req.method} ${req.url} from ${clientIp}`);

    const handler = this.authService.getNodeHandler();

    try {
      await handler(req, res);

      // BetterAuth writes responses directly — catch silent 500s that bypass NestJS error handling
      if (res.statusCode >= 500) {
        this.logger.error(
          `BetterAuth returned ${String(res.statusCode)} for ${req.method} ${req.url} from ${clientIp}` +
            ` — check container stdout for '# SERVER_ERROR' details`
        );
      }
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      const stack = error instanceof Error ? error.stack : undefined;

      this.logger.error(
        `BetterAuth handler error: ${req.method} ${req.url} from ${clientIp} - ${message}`,
        stack
      );

      if (!res.headersSent) {
        const mappedError = this.mapToHttpException(error);
        if (mappedError) {
          throw mappedError;
        }

        throw new HttpException(
          "Unable to complete authentication. Please try again in a moment.",
          HttpStatus.INTERNAL_SERVER_ERROR
        );
      }

      this.logger.error(
        `Headers already sent for failed auth request ${req.method} ${req.url} — client may have received partial response`
      );
    }
  }

  /**
   * Extract client IP from request, handling proxies
   */
  private getClientIp(req: ExpressRequest): string {
    // Check X-Forwarded-For header (for reverse proxy setups)
    const forwardedFor = req.headers["x-forwarded-for"];
    if (forwardedFor) {
      const ips = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor;
      return ips?.split(",")[0]?.trim() ?? "unknown";
    }

    // Fall back to direct IP
    return req.ip ?? req.socket.remoteAddress ?? "unknown";
  }

  /**
   * Preserve known HTTP errors from BetterAuth/better-call instead of converting
   * every failure into a generic 500.
   */
  private mapToHttpException(error: unknown): HttpException | null {
    if (error instanceof HttpException) {
      return error;
    }

    if (!error || typeof error !== "object") {
      return null;
    }

    const statusCode = "statusCode" in error ? error.statusCode : undefined;
    if (!this.isHttpStatus(statusCode)) {
      return null;
    }

    const responseBody = "body" in error && error.body !== undefined ? error.body : undefined;
    if (
      responseBody !== undefined &&
      responseBody !== null &&
      (typeof responseBody === "string" || typeof responseBody === "object")
    ) {
      return new HttpException(responseBody, statusCode);
    }

    const message =
      "message" in error && typeof error.message === "string" && error.message.length > 0
        ? error.message
        : "Authentication request failed";
    return new HttpException(message, statusCode);
  }

  private isHttpStatus(value: unknown): value is number {
    if (typeof value !== "number" || !Number.isInteger(value)) {
      return false;
    }
    return value >= 400 && value <= 599;
  }
}
