import { Inject, Injectable } from "@nestjs/common";
import { Logger } from "nestjs-pino";

import {
  ACCOUNT_STATUS,
  type AccountStatus,
  type SendVerificationCodeRequest,
  type SendVerificationCodeResponse,
  type VerifyCodeRequest,
  type VerifyCodeResponse,
} from "@customer-portal/domain/get-started";

import { UsersService } from "@bff/modules/users/application/users.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { SalesforceAccountService } from "@bff/integrations/salesforce/services/salesforce-account.service.js";
import { WhmcsAccountDiscoveryService } from "@bff/integrations/whmcs/services/whmcs-account-discovery.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";

import { OtpService } from "../otp/otp.service.js";
import { OtpEmailService } from "../otp/otp-email.service.js";
import { GetStartedSessionService } from "../otp/get-started-session.service.js";

/**
 * Result of account status detection across Portal, WHMCS, and Salesforce.
 * Optional fields are populated based on the detected status:
 * - WHMCS_UNMAPPED: whmcsClientId, whmcsFirstName, whmcsLastName
 * - SF_UNMAPPED: sfAccountId, sfFirstName, sfLastName, sfAddress
 */
interface AccountStatusResult {
  status: AccountStatus;
  sfAccountId?: string;
  sfFirstName?: string;
  sfLastName?: string;
  sfAddress?: {
    prefectureJa?: string;
    cityJa?: string;
    townJa?: string;
    streetAddress?: string;
    postcode?: string;
  };
  whmcsClientId?: number;
  whmcsFirstName?: string;
  whmcsLastName?: string;
}

/**
 * Verification Workflow Service
 *
 * Handles OTP send/verify and account status detection.
 * No distributed transaction needed — simple request-response operations.
 */
@Injectable()
export class VerificationWorkflowService {
  constructor(
    private readonly otpService: OtpService,
    private readonly otpEmailService: OtpEmailService,
    private readonly sessionService: GetStartedSessionService,
    private readonly usersService: UsersService,
    private readonly mappingsService: MappingsService,
    private readonly salesforceAccountService: SalesforceAccountService,
    private readonly whmcsDiscovery: WhmcsAccountDiscoveryService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Send OTP verification code to email
   */
  async sendCode(
    request: SendVerificationCodeRequest,
    fingerprint?: string
  ): Promise<SendVerificationCodeResponse> {
    const { email } = request;
    const normalizedEmail = email.toLowerCase().trim();

    try {
      // Generate OTP and store in Redis (with fingerprint for binding)
      const code = await this.otpService.generateAndStore(normalizedEmail, fingerprint);

      // Create session for this verification flow (token returned in verifyCode)
      await this.sessionService.create(normalizedEmail);

      // Send email with OTP code
      await this.otpEmailService.sendOtpCode(normalizedEmail, code, "verification");

      this.logger.log({ email: normalizedEmail }, "OTP verification code sent");

      return {
        sent: true,
        message: "Verification code sent to your email",
      };
    } catch (error) {
      this.logger.error(
        { error: extractErrorMessage(error), email: normalizedEmail },
        "Failed to send verification code"
      );

      return {
        sent: false,
        message: "Failed to send verification code. Please try again.",
      };
    }
  }

  /**
   * Verify OTP code and determine account status
   */
  async verifyCode(request: VerifyCodeRequest, fingerprint?: string): Promise<VerifyCodeResponse> {
    const { email, code, handoffToken } = request;
    const normalizedEmail = email.toLowerCase().trim();

    // Verify OTP (with fingerprint check - logs warning if different context)
    const otpResult = await this.otpService.verify(normalizedEmail, code, fingerprint);

    if (!otpResult.valid) {
      return this.buildOtpErrorResponse(otpResult);
    }

    // Create verified session
    const sessionToken = await this.sessionService.create(normalizedEmail);

    // Check account status across all systems
    const discoveredStatus = await this.determineAccountStatus(normalizedEmail);

    // Get prefill data (including handoff token data if provided)
    const { prefill, sfAccountId: handoffSfAccountId } = await this.resolvePrefillData(
      normalizedEmail,
      discoveredStatus,
      handoffToken
    );

    // Merge handoff SF account ID if discovery didn't find one
    const accountStatus: AccountStatusResult =
      !discoveredStatus.sfAccountId && handoffSfAccountId
        ? { ...discoveredStatus, sfAccountId: handoffSfAccountId }
        : discoveredStatus;

    // Update session with verified status and account info
    const prefillData = this.buildSessionPrefillData(prefill, accountStatus);
    await this.sessionService.markEmailVerified(
      sessionToken,
      accountStatus.status,
      Object.keys(prefillData).length > 0 ? prefillData : undefined
    );

    this.logger.log(
      { email: normalizedEmail, accountStatus: accountStatus.status, hasHandoff: !!handoffToken },
      "Email verified and account status determined"
    );

    return {
      verified: true,
      sessionToken,
      accountStatus: accountStatus.status,
      prefill,
    };
  }

  // ---------------------------------------------------------------------------
  // Private helpers
  // ---------------------------------------------------------------------------

  private buildOtpErrorResponse(otpResult: {
    reason?: "expired" | "invalid" | "max_attempts";
    attemptsRemaining?: number;
  }): VerifyCodeResponse {
    let error: string;
    switch (otpResult.reason) {
      case "expired":
        error = "Code expired. Please request a new one.";
        break;
      case "max_attempts":
        error = "Too many failed attempts. Please request a new code.";
        break;
      default:
        error = "Invalid code. Please try again.";
    }

    return {
      verified: false,
      error,
      attemptsRemaining: otpResult.attemptsRemaining,
    };
  }

  private async determineAccountStatus(email: string): Promise<AccountStatusResult> {
    // Check Portal user first
    const portalUser = await this.usersService.findByEmailInternal(email);
    if (portalUser) {
      const hasMapping = await this.mappingsService.hasMapping(portalUser.id);
      if (hasMapping) {
        return { status: ACCOUNT_STATUS.PORTAL_EXISTS };
      }
    }

    // Check WHMCS client or user (sub-account)
    const whmcsClient = await this.whmcsDiscovery.findAccountByEmail(email);
    if (whmcsClient) {
      const mapping = await this.mappingsService.findByWhmcsClientId(whmcsClient.id);
      if (mapping) {
        return { status: ACCOUNT_STATUS.PORTAL_EXISTS };
      }
      return {
        status: ACCOUNT_STATUS.WHMCS_UNMAPPED,
        whmcsClientId: whmcsClient.id,
        ...(whmcsClient.firstname && { whmcsFirstName: whmcsClient.firstname }),
        ...(whmcsClient.lastname && { whmcsLastName: whmcsClient.lastname }),
      };
    }

    // Check Salesforce account
    const sfAccount = await this.salesforceAccountService.findByEmail(email);
    if (sfAccount) {
      const mapping = await this.mappingsService.findBySfAccountId(sfAccount.id);
      if (mapping) {
        return { status: ACCOUNT_STATUS.PORTAL_EXISTS };
      }
      return {
        status: ACCOUNT_STATUS.SF_UNMAPPED,
        sfAccountId: sfAccount.id,
        ...(sfAccount.firstName && { sfFirstName: sfAccount.firstName }),
        ...(sfAccount.lastName && { sfLastName: sfAccount.lastName }),
        ...(sfAccount.address && {
          sfAddress: {
            prefectureJa: sfAccount.address.state,
            cityJa: sfAccount.address.city,
            // MailingStreet contains townJa + streetAddress concatenated — store as-is
            streetAddress: sfAccount.address.address1,
            postcode: sfAccount.address.postcode,
          },
        }),
      };
    }

    return { status: ACCOUNT_STATUS.NEW_CUSTOMER };
  }

  private getPrefillData(
    email: string,
    accountStatus: AccountStatusResult
  ): VerifyCodeResponse["prefill"] {
    if (accountStatus.status === ACCOUNT_STATUS.WHMCS_UNMAPPED) {
      return {
        email,
        ...(accountStatus.whmcsFirstName && { firstName: accountStatus.whmcsFirstName }),
        ...(accountStatus.whmcsLastName && { lastName: accountStatus.whmcsLastName }),
      };
    }
    if (accountStatus.status === ACCOUNT_STATUS.SF_UNMAPPED && accountStatus.sfAccountId) {
      return {
        email,
        ...(accountStatus.sfFirstName && { firstName: accountStatus.sfFirstName }),
        ...(accountStatus.sfLastName && { lastName: accountStatus.sfLastName }),
        ...(accountStatus.sfAddress && { address: accountStatus.sfAddress }),
      };
    }
    return undefined;
  }

  private async resolvePrefillData(
    email: string,
    accountStatus: AccountStatusResult,
    handoffToken?: string
  ): Promise<{ prefill: VerifyCodeResponse["prefill"]; sfAccountId?: string }> {
    const prefill = this.getPrefillData(email, accountStatus);

    if (!handoffToken) {
      return { prefill };
    }

    const handoffData = await this.sessionService.getGuestHandoffToken(handoffToken, email);

    if (!handoffData) {
      return { prefill };
    }

    this.logger.debug({ email, handoffToken }, "Applying handoff token data to session");

    const mergedPrefill = {
      ...prefill,
      firstName: handoffData.firstName,
      lastName: handoffData.lastName,
      phone: handoffData.phone ?? prefill?.phone,
      address: handoffData.address,
    };

    await this.sessionService.invalidateHandoffToken(handoffToken);

    return { prefill: mergedPrefill, sfAccountId: handoffData.sfAccountId };
  }

  private buildSessionPrefillData(
    prefill: VerifyCodeResponse["prefill"],
    accountStatus: AccountStatusResult
  ): Record<string, unknown> {
    return {
      ...(prefill?.firstName && { firstName: prefill.firstName }),
      ...(prefill?.lastName && { lastName: prefill.lastName }),
      ...(prefill?.phone && { phone: prefill.phone }),
      ...(prefill?.address && { address: prefill.address }),
      ...(accountStatus.sfAccountId && { sfAccountId: accountStatus.sfAccountId }),
      ...(accountStatus.whmcsClientId !== undefined && {
        whmcsClientId: accountStatus.whmcsClientId,
      }),
      ...(prefill?.eligibilityStatus && { eligibilityStatus: prefill.eligibilityStatus }),
    };
  }
}
