import { BadRequestException, ConflictException, Inject, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";
import * as argon2 from "argon2";

import { type MigrateWhmcsAccountRequest } from "@customer-portal/domain/get-started";
import { ErrorCode } from "@customer-portal/domain/common";
import {
  getCustomFieldValue,
  serializeWhmcsKeyValueMap,
} from "@customer-portal/domain/customer/providers";

import { DistributedLockService } from "@bff/infra/cache/distributed-lock.service.js";
import { UsersService } from "@bff/modules/users/application/users.service.js";
import { SalesforceAccountService } from "@bff/integrations/salesforce/services/salesforce-account.service.js";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { WhmcsAccountDiscoveryService } from "@bff/integrations/whmcs/services/whmcs-account-discovery.service.js";
import { WhmcsClientService } from "@bff/integrations/whmcs/services/whmcs-client.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { PORTAL_SOURCE_MIGRATED } from "@bff/modules/auth/constants/portal.constants.js";
import type { AuthResultInternal } from "@bff/modules/auth/auth.types.js";

import { GetStartedSessionService } from "../otp/get-started-session.service.js";
import {
  UpdateSalesforceFlagsStep,
  GenerateAuthResultStep,
  CreatePortalUserStep,
} from "./steps/index.js";

/** WHMCS client update payload for account migration (password + optional custom fields) */
interface WhmcsMigrationClientUpdate {
  password2: string;
  customfields?: string;
}

/**
 * Account Migration Workflow Service
 *
 * Handles WHMCS_UNMAPPED path — passwordless migration for users
 * who already have a WHMCS account. Email verification serves as
 * identity proof. Creates Portal user + SF mapping.
 */
@Injectable()
export class AccountMigrationWorkflowService {
  constructor(
    private readonly config: ConfigService,
    private readonly sessionService: GetStartedSessionService,
    private readonly lockService: DistributedLockService,
    private readonly usersService: UsersService,
    private readonly salesforceAccountService: SalesforceAccountService,
    private readonly salesforceFacade: SalesforceFacade,
    private readonly whmcsDiscovery: WhmcsAccountDiscoveryService,
    private readonly whmcsClientService: WhmcsClientService,
    private readonly portalUserStep: CreatePortalUserStep,
    private readonly sfFlagsStep: UpdateSalesforceFlagsStep,
    private readonly authResultStep: GenerateAuthResultStep,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Migrate WHMCS account to portal without legacy password validation.
   *
   * Flow: validate session → verify WHMCS client → find SF account →
   * update WHMCS password → create portal user → update SF flags → auth tokens
   */
  async execute(request: MigrateWhmcsAccountRequest): Promise<AuthResultInternal> {
    const sessionResult = await this.sessionService.acquireAndMarkAsUsed(
      request.sessionToken,
      "migrate_whmcs_account"
    );

    if (!sessionResult.success) {
      throw new BadRequestException(sessionResult.reason);
    }

    const session = sessionResult.session;

    if (!session.whmcsClientId) {
      throw new BadRequestException(
        "No WHMCS account found in session. Please verify your email again."
      );
    }

    const { email, whmcsClientId } = session as { email: string; whmcsClientId: number };
    const { password, dateOfBirth, gender } = request;
    const lockKey = `migrate-whmcs:${email}`;

    try {
      return await this.lockService.withLock(
        lockKey,
        async () =>
          this.executeMigration(
            { email, whmcsClientId },
            request.sessionToken,
            password,
            dateOfBirth,
            gender
          ),
        { ttlMs: 60_000 }
      );
    } catch (error) {
      this.logger.error({ error: extractErrorMessage(error), email }, "WHMCS migration failed");
      throw error;
    }
  }

  private async executeMigration(
    session: { email: string; whmcsClientId: number },
    sessionToken: string,
    password: string,
    dateOfBirth?: string,
    gender?: string
  ): Promise<AuthResultInternal> {
    const { email, whmcsClientId } = session;

    // Verify WHMCS client still exists and matches session
    const whmcsClient = await this.whmcsDiscovery.findAccountByEmail(email);
    if (!whmcsClient || whmcsClient.id !== whmcsClientId) {
      throw new BadRequestException({
        code: ErrorCode.VALIDATION_FAILED,
        message: `WHMCS client mismatch for email ${email}: expected ${whmcsClientId}, got ${whmcsClient?.id ?? "none"}`,
      });
    }

    // Check for existing portal user
    const existingPortalUser = await this.usersService.findByEmailInternal(email);
    if (existingPortalUser) {
      throw new ConflictException("An account already exists. Please log in.");
    }

    // Find Salesforce account for mapping
    const sfAccount = await this.findSalesforceAccountForMigration(email, whmcsClientId);
    if (!sfAccount) {
      throw new BadRequestException({
        code: ErrorCode.CUSTOMER_NOT_FOUND,
        message: `No Salesforce account found for migration: email=${email}, whmcsClientId=${whmcsClientId}`,
      });
    }

    // Hash password for portal storage
    const passwordHash = await argon2.hash(password);

    // Update WHMCS client with new password (DOB/gender only if provided)
    await this.updateWhmcsClientForMigration(whmcsClientId, password, dateOfBirth, gender);

    // Create portal user and ID mapping
    const { userId } = await this.portalUserStep.execute({
      email,
      passwordHash,
      whmcsClientId,
      sfAccountId: sfAccount.id,
    });

    // Update Salesforce portal flags (DEGRADABLE — step handles its own errors)
    await this.sfFlagsStep.execute({
      sfAccountId: sfAccount.id,
      whmcsClientId,
      source: PORTAL_SOURCE_MIGRATED,
    });

    // Generate auth result
    const authResult = await this.authResultStep.execute({
      userId,
      email,
      auditSource: "whmcs_migration",
      auditDetails: { whmcsClientId },
    });

    // Invalidate session
    await this.sessionService.invalidate(sessionToken);

    this.logger.log({ email, userId, whmcsClientId }, "WHMCS account migrated successfully");

    return authResult;
  }

  private async findSalesforceAccountForMigration(
    email: string,
    whmcsClientId: number
  ): Promise<{ id: string } | null> {
    try {
      // First try by email
      const sfAccount = await this.salesforceAccountService.findByEmail(email);
      if (sfAccount) {
        return { id: sfAccount.id };
      }

      // Try by customer number from WHMCS
      const whmcsClient = await this.whmcsClientService.getClientDetails(whmcsClientId);

      const customerNumber =
        getCustomFieldValue(whmcsClient.customfields, "198")?.trim() ??
        getCustomFieldValue(whmcsClient.customfields, "Customer Number")?.trim();

      if (!customerNumber) {
        this.logger.warn(
          { whmcsClientId, email },
          "No customer number found in WHMCS for SF lookup"
        );
        return null;
      }

      const sfAccountByNumber =
        await this.salesforceFacade.findAccountByCustomerNumber(customerNumber);
      if (sfAccountByNumber) {
        return { id: sfAccountByNumber.id };
      }

      this.logger.warn(
        { whmcsClientId, email, customerNumber },
        "No Salesforce account found for WHMCS migration"
      );
      return null;
    } catch (error) {
      this.logger.warn(
        { error: extractErrorMessage(error), email, whmcsClientId },
        "Failed to find Salesforce account for migration"
      );
      return null;
    }
  }

  private async updateWhmcsClientForMigration(
    clientId: number,
    password: string,
    dateOfBirth?: string,
    gender?: string
  ): Promise<void> {
    const dobFieldId = this.config.get<string>("WHMCS_DOB_FIELD_ID");
    const genderFieldId = this.config.get<string>("WHMCS_GENDER_FIELD_ID");

    const customfieldsMap: Record<string, string> = {};
    if (dobFieldId && dateOfBirth) customfieldsMap[dobFieldId] = dateOfBirth;
    if (genderFieldId && gender) customfieldsMap[genderFieldId] = gender;

    const updateData: WhmcsMigrationClientUpdate = {
      password2: password,
    };

    if (Object.keys(customfieldsMap).length > 0) {
      updateData.customfields = serializeWhmcsKeyValueMap(customfieldsMap);
    }

    // customfields is sent as base64-encoded serialized PHP array to the WHMCS API,
    // which differs from the parsed client schema type — cast is intentional
    await this.whmcsClientService.updateClient(
      clientId,
      updateData as unknown as Parameters<typeof this.whmcsClientService.updateClient>[1]
    );

    this.logger.log({ clientId }, "Updated WHMCS client with new password and profile data");
  }
}
