import { Body, Controller, Logger, Post } from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
import { Public } from "@edr/api-common";

import {
  ForgotPasswordRequestDto,
  ForgotPasswordVerifyDto,
  ResolveResetLinkDto,
} from "./dto/forgot-password.dto";
import {
  ForgotPasswordService,
  ResetLinkAccount,
  ResetTicket,
} from "./forgot-password.service";

/**
 * Freight-owned reset flow. IAM ships a `forgot-password` route, but it only
 * ever SMSes a magic link (no email channel, and it needs `FE_BASE_URL`, which
 * this API does not set). These routes drive freight's own email-or-phone OTP
 * service instead, then hand back a ticket for IAM's public `set-password`.
 */
@ApiTags("auth")
@Controller("auth")
@Public()
export class ForgotPasswordController {
  private readonly logger = new Logger(ForgotPasswordController.name);

  constructor(private readonly forgotPasswordService: ForgotPasswordService) {}

  @Post("forgot-password/request")
  @ApiOperation({
    summary: "Send a password-reset code to the account's email AND phone",
    description:
      "One code, delivered over every contact the account has; either delivery " +
      "verifies it. Always reports success — an unknown, inactive, or contactless " +
      "account is indistinguishable from a real one, so this cannot be used to " +
      "enumerate accounts.",
  })
  async request(@Body() dto: ForgotPasswordRequestDto): Promise<{ success: true }> {
    const user = await this.forgotPasswordService.resolveActiveUser(dto.identifier);

    if (user) {
      try {
        await this.forgotPasswordService.requestReset(user);
      } catch (error) {
        // A delivery failure must not change the response shape either — log it
        // and let the caller sit on the OTP screen.
        this.logger.error(
          `Reset code delivery failed for user ${user.id}: ${
            error instanceof Error ? error.message : String(error)
          }`,
          error instanceof Error ? error.stack : undefined,
        );
      }
    } else {
      this.logger.log("Reset requested for an unknown or inactive account");
    }

    return { success: true };
  }

  @Post("forgot-password/verify")
  @ApiOperation({
    summary: "Exchange a valid reset code for a single-use set-password ticket",
    description:
      "The returned { userId, verificationCode } is the body for PATCH /api/auth/set-password, " +
      "alongside the same identifier and the new password.",
  })
  verify(@Body() dto: ForgotPasswordVerifyDto): Promise<ResetTicket> {
    return this.forgotPasswordService.verifyAndMintTicket(dto.identifier, dto.otp);
  }

  @Post("forgot-password/resolve-link")
  @ApiOperation({
    summary: "Validate a staff-issued reset link and return its set-password ticket",
    description:
      "Takes the link's uid/token pair. The returned { userId, identifier, verificationCode } " +
      "is the body for PATCH /api/auth/set-password, so the customer never types an identifier. " +
      "A bad or expired link is rejected here rather than after the password is typed.",
  })
  resolveLink(@Body() dto: ResolveResetLinkDto): Promise<ResetLinkAccount> {
    return this.forgotPasswordService.resolveResetLink(dto.userId, dto.token);
  }
}
