import {
  Controller,
  Post,
  Body,
  UseGuards,
  UseInterceptors,
  Get,
  Req,
  HttpCode,
  UsePipes,
  Res,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { Throttle } from "@nestjs/throttler";
import { JwtService } from "@nestjs/jwt";
import { AuthFacade } from "@bff/modules/auth/application/auth.facade";
import { LocalAuthGuard } from "./guards/local-auth.guard";
import { AuthThrottleGuard } from "./guards/auth-throttle.guard";
import {
  FailedLoginThrottleGuard,
  type RequestWithRateLimit,
} from "./guards/failed-login-throttle.guard";
import { LoginResultInterceptor } from "./interceptors/login-result.interceptor";
import { Public } from "../../decorators/public.decorator";
import { ZodValidationPipe } from "@customer-portal/validation/nestjs";
import type { RequestWithUser } from "@bff/modules/auth/auth.types";
import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-read-throttle.guard";
import { SalesforceWriteThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-write-throttle.guard";

// Import Zod schemas from domain
import {
  signupRequestSchema,
  passwordResetRequestSchema,
  passwordResetSchema,
  setPasswordRequestSchema,
  linkWhmcsRequestSchema,
  changePasswordRequestSchema,
  validateSignupRequestSchema,
  accountStatusRequestSchema,
  ssoLinkRequestSchema,
  checkPasswordNeededRequestSchema,
  refreshTokenRequestSchema,
  checkPasswordNeededResponseSchema,
  linkWhmcsResponseSchema,
  type SignupRequest,
  type PasswordResetRequest,
  type ResetPasswordRequest,
  type SetPasswordRequest,
  type LinkWhmcsRequest,
  type ChangePasswordRequest,
  type ValidateSignupRequest,
  type AccountStatusRequest,
  type SsoLinkRequest,
  type CheckPasswordNeededRequest,
  type RefreshTokenRequest,
  type AuthTokens,
} from "@customer-portal/domain/auth";

type CookieValue = string | undefined;
type RequestWithCookies = Omit<Request, "cookies"> & {
  cookies?: Record<string, CookieValue>;
};

const resolveAuthorizationHeader = (req: RequestWithCookies): string | undefined => {
  const rawHeader = req.headers?.authorization;

  if (typeof rawHeader === "string") {
    return rawHeader;
  }

  if (Array.isArray(rawHeader)) {
    const headerValues: string[] = rawHeader;
    for (const candidate of headerValues) {
      if (typeof candidate === "string" && candidate.trim().length > 0) {
        return candidate;
      }
    }
  }

  return undefined;
};

const extractBearerToken = (req: RequestWithCookies): string | undefined => {
  const authHeader = resolveAuthorizationHeader(req);
  if (authHeader && authHeader.startsWith("Bearer ")) {
    return authHeader.slice(7);
  }
  return undefined;
};

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

const calculateCookieMaxAge = (isoTimestamp: string): number => {
  const expiresAt = Date.parse(isoTimestamp);
  if (Number.isNaN(expiresAt)) {
    return 0;
  }
  return Math.max(0, expiresAt - Date.now());
};

@Controller("auth")
export class AuthController {
  constructor(
    private authFacade: AuthFacade,
    private readonly jwtService: JwtService
  ) {}

  private setAuthCookies(res: Response, tokens: AuthTokens): void {
    const accessMaxAge = calculateCookieMaxAge(tokens.expiresAt);
    const refreshMaxAge = calculateCookieMaxAge(tokens.refreshExpiresAt);

    res.setSecureCookie("access_token", tokens.accessToken, {
      maxAge: accessMaxAge,
      path: "/",
    });
    res.setSecureCookie("refresh_token", tokens.refreshToken, {
      maxAge: refreshMaxAge,
      path: "/",
    });
  }

  private clearAuthCookies(res: Response): void {
    res.setSecureCookie("access_token", "", { maxAge: 0, path: "/" });
    res.setSecureCookie("refresh_token", "", { maxAge: 0, path: "/" });
  }

  private applyAuthRateLimitHeaders(req: RequestWithRateLimit, res: Response): void {
    FailedLoginThrottleGuard.applyRateLimitHeaders(req, res);
  }

  @Public()
  @Post("validate-signup")
  @UseGuards(AuthThrottleGuard, SalesforceReadThrottleGuard)
  @Throttle({ default: { limit: 20, ttl: 600 } }) // 20 validations per 10 minutes per IP
  @UsePipes(new ZodValidationPipe(validateSignupRequestSchema))
  async validateSignup(@Body() validateData: ValidateSignupRequest, @Req() req: Request) {
    return this.authFacade.validateSignup(validateData, req);
  }

  @Public()
  @Get("health-check")
  async healthCheck() {
    return this.authFacade.healthCheck();
  }

  @Public()
  @Post("signup-preflight")
  @UseGuards(AuthThrottleGuard, SalesforceReadThrottleGuard)
  @Throttle({ default: { limit: 20, ttl: 600 } }) // 20 validations per 10 minutes per IP
  @UsePipes(new ZodValidationPipe(signupRequestSchema))
  @HttpCode(200)
  async signupPreflight(@Body() signupData: SignupRequest) {
    return this.authFacade.signupPreflight(signupData);
  }

  @Public()
  @Post("account-status")
  @UsePipes(new ZodValidationPipe(accountStatusRequestSchema))
  async accountStatus(@Body() body: AccountStatusRequest) {
    return this.authFacade.getAccountStatus(body.email);
  }

  @Public()
  @Post("signup")
  @UseGuards(AuthThrottleGuard, SalesforceWriteThrottleGuard)
  @Throttle({ default: { limit: 5, ttl: 900 } }) // 5 signups per 15 minutes per IP (reasonable for account creation)
  @UsePipes(new ZodValidationPipe(signupRequestSchema))
  async signup(
    @Body() signupData: SignupRequest,
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response
  ) {
    const result = await this.authFacade.signup(signupData, req);
    this.setAuthCookies(res, result.tokens);
    return result;
  }

  @Public()
  @UseGuards(LocalAuthGuard, FailedLoginThrottleGuard)
  @UseInterceptors(LoginResultInterceptor)
  @Post("login")
  async login(
    @Req() req: RequestWithUser & RequestWithRateLimit,
    @Res({ passthrough: true }) res: Response
  ) {
    const result = await this.authFacade.login(req.user, req);
    this.setAuthCookies(res, result.tokens);
    this.applyAuthRateLimitHeaders(req, res);
    return result;
  }

  @Public()
  @Post("logout")
  async logout(
    @Req() req: RequestWithCookies & { user?: { id: string } },
    @Res({ passthrough: true }) res: Response
  ) {
    const token = extractTokenFromRequest(req);
    let userId = req.user?.id;

    if (!userId && token) {
      try {
        const payload = await this.jwtService.verifyAsync<{ sub?: string }>(token, {
          ignoreExpiration: true,
        });

        if (payload?.sub) {
          userId = payload.sub;
        }
      } catch {
        // Ignore verification errors – we still want to clear client cookies.
      }
    }

    await this.authFacade.logout(userId, token, req as Request);

    // Always clear cookies, even if session expired
    this.clearAuthCookies(res);
    return { message: "Logout successful" };
  }

  @Public()
  @Post("refresh")
  @Throttle({ default: { limit: 10, ttl: 300 } }) // 10 attempts per 5 minutes per IP
  @UsePipes(new ZodValidationPipe(refreshTokenRequestSchema))
  async refreshToken(
    @Body() body: RefreshTokenRequest,
    @Req() req: RequestWithCookies,
    @Res({ passthrough: true }) res: Response
  ) {
    const refreshToken = body.refreshToken ?? req.cookies?.refresh_token;
    const rawUserAgent = req.headers["user-agent"];
    const userAgent = typeof rawUserAgent === "string" ? rawUserAgent : undefined;
    const result = await this.authFacade.refreshTokens(refreshToken, {
      deviceId: body.deviceId,
      userAgent,
    });
    this.setAuthCookies(res, result.tokens);
    return result;
  }

  @Public()
  @Post("link-whmcs")
  @UseGuards(AuthThrottleGuard, SalesforceWriteThrottleGuard)
  @Throttle({ default: { limit: 5, ttl: 600 } }) // 5 attempts per 10 minutes per IP (industry standard)
  @UsePipes(new ZodValidationPipe(linkWhmcsRequestSchema))
  async linkWhmcs(@Body() linkData: LinkWhmcsRequest, @Req() _req: Request) {
    const result = await this.authFacade.linkWhmcsUser(linkData);
    return linkWhmcsResponseSchema.parse(result);
  }

  @Public()
  @Post("set-password")
  @UseGuards(AuthThrottleGuard)
  @Throttle({ default: { limit: 5, ttl: 600 } }) // 5 attempts per 10 minutes per IP+UA (industry standard)
  @UsePipes(new ZodValidationPipe(setPasswordRequestSchema))
  async setPassword(
    @Body() setPasswordData: SetPasswordRequest,
    @Req() _req: Request,
    @Res({ passthrough: true }) res: Response
  ) {
    const result = await this.authFacade.setPassword(setPasswordData);
    this.setAuthCookies(res, result.tokens);
    return result;
  }

  @Public()
  @Post("check-password-needed")
  @UsePipes(new ZodValidationPipe(checkPasswordNeededRequestSchema))
  @HttpCode(200)
  async checkPasswordNeeded(@Body() data: CheckPasswordNeededRequest) {
    const response = await this.authFacade.checkPasswordNeeded(data.email);
    return checkPasswordNeededResponseSchema.parse(response);
  }

  @Public()
  @Post("request-password-reset")
  @Throttle({ default: { limit: 5, ttl: 900 } }) // 5 attempts per 15 minutes (standard for password operations)
  @UsePipes(new ZodValidationPipe(passwordResetRequestSchema))
  async requestPasswordReset(@Body() body: PasswordResetRequest, @Req() req: Request) {
    await this.authFacade.requestPasswordReset(body.email, req);
    return { message: "If an account exists, a reset email has been sent" };
  }

  @Public()
  @Post("reset-password")
  @HttpCode(200)
  @UsePipes(new ZodValidationPipe(passwordResetSchema))
  async resetPassword(
    @Body() body: ResetPasswordRequest,
    @Res({ passthrough: true }) res: Response
  ) {
    await this.authFacade.resetPassword(body.token, body.password);

    // Clear auth cookies after password reset to force re-login
    this.clearAuthCookies(res);
    return { message: "Password reset successful" };
  }

  @Post("change-password")
  @Throttle({ default: { limit: 5, ttl: 300 } })
  @UsePipes(new ZodValidationPipe(changePasswordRequestSchema))
  async changePassword(
    @Req() req: Request & { user: { id: string } },
    @Body() body: ChangePasswordRequest,
    @Res({ passthrough: true }) res: Response
  ) {
    const result = await this.authFacade.changePassword(req.user.id, body, req);
    this.setAuthCookies(res, result.tokens);
    return result;
  }

  @Get("me")
  getAuthStatus(@Req() req: Request & { user: { id: string; email: string; role: string } }) {
    // Return basic auth info only - full profile should use /api/me
    return {
      isAuthenticated: true,
      user: {
        id: req.user.id,
        email: req.user.email,
        role: req.user.role,
      },
    };
  }

  @Post("sso-link")
  @UsePipes(new ZodValidationPipe(ssoLinkRequestSchema))
  async createSsoLink(
    @Req() req: Request & { user: { id: string } },
    @Body() body: SsoLinkRequest
  ) {
    const destination = body?.destination;
    return this.authFacade.createSsoLink(req.user.id, destination);
  }
}
