import {
  Controller,
  Post,
  Body,
  UseGuards,
  UseInterceptors,
  Get,
  Req,
  HttpCode,
  Res,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { RateLimitGuard, RateLimit } from "@bff/core/rate-limiting/index.js";
import { AuthOrchestrator } from "@bff/modules/auth/application/auth-orchestrator.service.js";
import { LocalAuthGuard } from "./guards/local-auth.guard.js";
import {
  FailedLoginThrottleGuard,
  type RequestWithRateLimit,
} from "./guards/failed-login-throttle.guard.js";
import { LoginResultInterceptor } from "./interceptors/login-result.interceptor.js";
import { Public, OptionalAuth } from "../../decorators/public.decorator.js";
import { createZodDto, ZodResponse } from "nestjs-zod";
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
import { SalesforceWriteThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-write-throttle.guard.js";
import { JoseJwtService } from "../../infra/token/jose-jwt.service.js";
import type { UserAuth } from "@customer-portal/domain/customer";
import { extractAccessTokenFromRequest } from "../../utils/token-from-request.util.js";
import {
  setAuthCookies,
  clearAuthCookies,
  buildSessionInfo,
  ACCESS_COOKIE_PATH,
  REFRESH_COOKIE_PATH,
  TOKEN_TYPE,
} from "./utils/auth-cookie.util.js";

// Import Zod schemas from domain
import {
  signupRequestSchema,
  passwordResetRequestSchema,
  passwordResetSchema,
  setPasswordRequestSchema,
  linkWhmcsRequestSchema,
  changePasswordRequestSchema,
  accountStatusRequestSchema,
  ssoLinkRequestSchema,
  checkPasswordNeededRequestSchema,
  refreshTokenRequestSchema,
  checkPasswordNeededResponseSchema,
  linkWhmcsResponseSchema,
} from "@customer-portal/domain/auth";

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

// Re-export for backward compatibility with tests
export { ACCESS_COOKIE_PATH, REFRESH_COOKIE_PATH, TOKEN_TYPE };

class SignupRequestDto extends createZodDto(signupRequestSchema) {}
class AccountStatusRequestDto extends createZodDto(accountStatusRequestSchema) {}
class RefreshTokenRequestDto extends createZodDto(refreshTokenRequestSchema) {}
class LinkWhmcsRequestDto extends createZodDto(linkWhmcsRequestSchema) {}
class SetPasswordRequestDto extends createZodDto(setPasswordRequestSchema) {}
class CheckPasswordNeededRequestDto extends createZodDto(checkPasswordNeededRequestSchema) {}
class PasswordResetRequestDto extends createZodDto(passwordResetRequestSchema) {}
class ResetPasswordRequestDto extends createZodDto(passwordResetSchema) {}
class ChangePasswordRequestDto extends createZodDto(changePasswordRequestSchema) {}
class SsoLinkRequestDto extends createZodDto(ssoLinkRequestSchema) {}
class LinkWhmcsResponseDto extends createZodDto(linkWhmcsResponseSchema) {}
class CheckPasswordNeededResponseDto extends createZodDto(checkPasswordNeededResponseSchema) {}

@Controller("auth")
export class AuthController {
  constructor(
    private authOrchestrator: AuthOrchestrator,
    private readonly jwtService: JoseJwtService
  ) {}

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

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

  @Public()
  @Post("account-status")
  async accountStatus(@Body() body: AccountStatusRequestDto) {
    return this.authOrchestrator.getAccountStatus(body.email);
  }

  @Public()
  @Post("signup")
  @UseGuards(RateLimitGuard, SalesforceWriteThrottleGuard)
  @RateLimit({ limit: 5, ttl: 900 }) // 5 signups per 15 minutes per IP (reasonable for account creation)
  async signup(
    @Body() signupData: SignupRequestDto,
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response
  ) {
    const result = await this.authOrchestrator.signup(signupData, req);
    setAuthCookies(res, result.tokens);
    return { user: result.user, session: buildSessionInfo(result.tokens) };
  }

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

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

    if (!userId && token) {
      const payload = await this.jwtService.verifyAllowExpired<{ sub?: string }>(token);
      if (payload?.sub) {
        userId = payload.sub;
      }
    }

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

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

  @Public()
  @Post("refresh")
  @UseGuards(RateLimitGuard)
  @RateLimit({ limit: 10, ttl: 300 }) // 10 attempts per 5 minutes per IP
  async refreshToken(
    @Body() body: RefreshTokenRequestDto,
    @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.authOrchestrator.refreshTokens(refreshToken, {
      deviceId: body.deviceId ?? undefined,
      userAgent: userAgent ?? undefined,
    });
    setAuthCookies(res, result.tokens);
    return { user: result.user, session: buildSessionInfo(result.tokens) };
  }

  @Public()
  @Post("migrate")
  @UseGuards(RateLimitGuard, SalesforceWriteThrottleGuard)
  @RateLimit({ limit: 5, ttl: 600 }) // 5 attempts per 10 minutes per IP (industry standard)
  @ZodResponse({ status: 200, description: "Migrate/link account", type: LinkWhmcsResponseDto })
  async migrateAccount(@Body() linkData: LinkWhmcsRequestDto) {
    const result = await this.authOrchestrator.linkWhmcsUser(linkData);
    return result;
  }

  @Public()
  @Post("set-password")
  @UseGuards(RateLimitGuard)
  @RateLimit({ limit: 5, ttl: 600 }) // 5 attempts per 10 minutes per IP+UA (industry standard)
  async setPassword(
    @Body() setPasswordData: SetPasswordRequestDto,
    @Req() _req: Request,
    @Res({ passthrough: true }) res: Response
  ) {
    const result = await this.authOrchestrator.setPassword(setPasswordData);
    setAuthCookies(res, result.tokens);
    return { user: result.user, session: buildSessionInfo(result.tokens) };
  }

  @Public()
  @Post("check-password-needed")
  @HttpCode(200)
  @ZodResponse({
    status: 200,
    description: "Check if password is needed",
    type: CheckPasswordNeededResponseDto,
  })
  async checkPasswordNeeded(@Body() data: CheckPasswordNeededRequestDto) {
    const response = await this.authOrchestrator.checkPasswordNeeded(data.email);
    return response;
  }

  @Public()
  @Post("request-password-reset")
  @UseGuards(RateLimitGuard)
  @RateLimit({ limit: 5, ttl: 900 }) // 5 attempts per 15 minutes (standard for password operations)
  async requestPasswordReset(@Body() body: PasswordResetRequestDto, @Req() req: Request) {
    await this.authOrchestrator.requestPasswordReset(body.email, req);
    return { message: "If an account exists, a reset email has been sent" };
  }

  @Public()
  @Post("reset-password")
  @HttpCode(200)
  @UseGuards(RateLimitGuard)
  @RateLimit({ limit: 5, ttl: 900 }) // 5 attempts per 15 minutes (standard for password operations)
  async resetPassword(
    @Body() body: ResetPasswordRequestDto,
    @Res({ passthrough: true }) res: Response
  ) {
    await this.authOrchestrator.resetPassword(body.token, body.password);

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

  @Post("change-password")
  @UseGuards(RateLimitGuard)
  @RateLimit({ limit: 5, ttl: 300 }) // 5 attempts per 5 minutes
  async changePassword(
    @Req() req: Request & { user: { id: string } },
    @Body() body: ChangePasswordRequestDto,
    @Res({ passthrough: true }) res: Response
  ) {
    const result = await this.authOrchestrator.changePassword(req.user.id, body, req);
    setAuthCookies(res, result.tokens);
    return { user: result.user, session: buildSessionInfo(result.tokens) };
  }

  /**
   * GET /auth/me - Check authentication status
   *
   * Uses @OptionalAuth: returns isAuthenticated: false if not logged in,
   * 401 only if session cookie is present but expired/invalid
   */
  @OptionalAuth()
  @Get("me")
  getAuthStatus(@Req() req: Request & { user?: UserAuth }) {
    if (!req.user) {
      return { isAuthenticated: false };
    }
    return { isAuthenticated: true, user: req.user };
  }

  @Post("sso-link")
  async createSsoLink(
    @Req() req: Request & { user: { id: string } },
    @Body() body: SsoLinkRequestDto
  ) {
    const destination = body?.destination;
    return this.authOrchestrator.createSsoLink(req.user.id, destination);
  }
}
