import { ConflictException, Inject, Injectable } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import * as argon2 from "argon2";
import type { Request } from "express";
import { AuditService, AuditAction } from "@bff/infra/audit/audit.service.js";
import { UsersService } from "@bff/modules/users/application/users.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { AuthTokenService } from "../token/token.service.js";
import { AuthRateLimitService } from "../rate-limiting/auth-rate-limit.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import {
  signupRequestSchema,
  type SignupRequest,
  type ValidateSignupRequest,
} from "@customer-portal/domain/auth";
import { mapPrismaUserToDomain } from "@bff/infra/mappers/index.js";
import {
  PORTAL_SOURCE_NEW_SIGNUP,
  PORTAL_STATUS_ACTIVE,
  type PortalRegistrationSource,
} from "@bff/modules/auth/constants/portal.constants.js";
import type { AuthResultInternal } from "@bff/modules/auth/auth.types.js";
import {
  SignupAccountResolverService,
  SignupValidationService,
  SignupWhmcsService,
  SignupUserCreationService,
} from "./signup/index.js";

/**
 * Signup Workflow Service
 *
 * Orchestrates the signup process by coordinating:
 * - SignupValidationService: Validates customer numbers and preflight checks
 * - SignupAccountResolverService: Resolves or creates Salesforce accounts and contacts
 * - SignupWhmcsService: Creates WHMCS clients
 * - SignupUserCreationService: Creates portal users with ID mappings
 */
@Injectable()
export class SignupWorkflowService {
  constructor(
    private readonly usersService: UsersService,
    private readonly mappingsService: MappingsService,
    private readonly salesforceService: SalesforceFacade,
    private readonly auditService: AuditService,
    private readonly tokenService: AuthTokenService,
    private readonly authRateLimitService: AuthRateLimitService,
    private readonly accountResolver: SignupAccountResolverService,
    private readonly signupValidation: SignupValidationService,
    private readonly whmcsSignup: SignupWhmcsService,
    private readonly userCreation: SignupUserCreationService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Validate customer number for signup
   */
  async validateSignup(validateData: ValidateSignupRequest, request?: Request) {
    return this.signupValidation.validateCustomerNumber(validateData, request);
  }

  /**
   * Execute the full signup workflow
   */
  async signup(signupData: SignupRequest, request?: Request): Promise<AuthResultInternal> {
    if (request) {
      await this.authRateLimitService.consumeSignupAttempt(request);
    }

    // Validate signup data using schema
    signupRequestSchema.parse(signupData);

    const {
      email,
      password,
      firstName,
      lastName,
      company,
      phone,
      address,
      nationality,
      dateOfBirth,
      gender,
    } = signupData;

    // Check for existing portal user
    const existingUser = await this.usersService.findByEmailInternal(email);
    if (existingUser) {
      const mapped = await this.mappingsService.hasMapping(existingUser.id);
      const message = mapped
        ? "You already have an account. Please sign in."
        : "You already have an account with us. Please sign in to continue setup.";
      await this.auditService.logAuthEvent(
        AuditAction.SIGNUP,
        existingUser.id,
        { email, reason: mapped ? "mapped_user_exists" : "unmapped_user_exists" },
        request,
        false,
        message
      );
      throw new ConflictException(message);
    }

    const passwordHash = await argon2.hash(password);

    try {
      // Step 1: Check for existing WHMCS client before provisioning in Salesforce
      await this.whmcsSignup.checkExistingClient(email.toLowerCase().trim());

      // Step 2: Validate WHMCS data requirements
      this.whmcsSignup.validateAddressData(signupData);

      // Step 3: Resolve or create Salesforce account
      const { snapshot: accountSnapshot, customerNumber: customerNumberForWhmcs } =
        await this.accountResolver.resolveOrCreate(signupData);

      const normalizedCustomerNumber = this.accountResolver.normalizeCustomerNumber(
        signupData.sfNumber
      );
      if (normalizedCustomerNumber) {
        const existingMapping = await this.mappingsService.findBySfAccountId(accountSnapshot.id);
        if (existingMapping) {
          throw new ConflictException(
            "You already have an account. Please use the login page to access your existing account."
          );
        }
      }

      // Step 4: Create WHMCS client
      // Address fields are validated by validateAddressData, safe to assert non-null
      const whmcsClient = await this.whmcsSignup.createClient({
        firstName,
        lastName,
        email,
        password,
        ...(company ? { company } : {}),
        phone: phone,
        address: {
          address1: address!.address1!,
          ...(address?.address2 ? { address2: address.address2 } : {}),
          city: address!.city!,
          state: address!.state!,
          postcode: address!.postcode!,
          country: address!.country!,
        },
        customerNumber: customerNumberForWhmcs,
        ...(dateOfBirth ? { dateOfBirth } : {}),
        ...(gender ? { gender } : {}),
        ...(nationality ? { nationality } : {}),
      });

      // Step 5: Create user and mapping in database
      const { userId } = await this.userCreation.createUserWithMapping({
        email,
        passwordHash,
        whmcsClientId: whmcsClient.clientId,
        sfAccountId: accountSnapshot.id,
      });

      // Step 6: Fetch fresh user and generate tokens
      const freshUser = await this.usersService.findByIdInternal(userId);
      if (!freshUser) {
        throw new Error("Failed to load created user");
      }

      await this.auditService.logAuthEvent(
        AuditAction.SIGNUP,
        userId,
        { email, whmcsClientId: whmcsClient.clientId },
        request,
        true
      );

      const profile = mapPrismaUserToDomain(freshUser);
      const tokens = await this.tokenService.generateTokenPair({
        id: profile.id,
        email: profile.email,
      });

      // Step 7: Update Salesforce portal flags (non-blocking)
      await this.updateSalesforcePortalFlags(
        accountSnapshot.id,
        PORTAL_SOURCE_NEW_SIGNUP,
        whmcsClient.clientId
      );

      return {
        user: profile,
        tokens,
      };
    } catch (error) {
      await this.auditService.logAuthEvent(
        AuditAction.SIGNUP,
        undefined,
        { email, error: extractErrorMessage(error) },
        request,
        false,
        extractErrorMessage(error)
      );

      this.logger.error("Signup error", { error: extractErrorMessage(error) });
      throw error;
    }
  }

  /**
   * Preflight check before signup - delegates to SignupValidationService
   */
  async signupPreflight(signupData: SignupRequest) {
    const { email, sfNumber } = signupData;
    const preflightResult = await this.signupValidation.preflightCheck(email, sfNumber);

    // Return with additional 'ok' field for API compatibility
    return {
      ok: true,
      ...preflightResult,
    };
  }

  private async updateSalesforcePortalFlags(
    accountId: string,
    source: PortalRegistrationSource,
    whmcsAccountId?: number
  ): Promise<void> {
    try {
      await this.salesforceService.updateAccountPortalFields(accountId, {
        status: PORTAL_STATUS_ACTIVE,
        source,
        lastSignedInAt: new Date(),
        ...(whmcsAccountId === undefined ? {} : { whmcsAccountId }),
      });
    } catch (error) {
      this.logger.warn("Failed to update Salesforce portal flags after signup", {
        accountId,
        error: extractErrorMessage(error),
      });
    }
  }
}
