import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { ConfigService } from "@nestjs/config";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { SalesforceConnection } from "./salesforce-connection.service.js";
import type { SalesforceAccountRecord } from "@customer-portal/domain/customer/providers";
import type { SalesforceResponse } from "@customer-portal/domain/common/providers";
import { customerNumberSchema, salesforceIdSchema } from "@customer-portal/domain/common";

/**
 * Salesforce Account Service
 *
 * Only contains methods that are actually used in the codebase:
 * - findByCustomerNumber() - Used in signup/WHMCS linking workflows
 * - getAccountDetails() - Used in signup to check WH_Account__c field
 */
@Injectable()
export class SalesforceAccountService {
  constructor(
    private connection: SalesforceConnection,
    private readonly configService: ConfigService,
    @Inject(Logger) private readonly logger: Logger
  ) {
    this.portalStatusField =
      this.configService.get<string>("ACCOUNT_PORTAL_STATUS_FIELD") ?? "Portal_Status__c";
    this.portalSourceField =
      this.configService.get<string>("ACCOUNT_PORTAL_STATUS_SOURCE_FIELD") ??
      "Portal_Registration_Source__c";
    this.portalLastSignedInField =
      this.configService.get<string>("ACCOUNT_PORTAL_LAST_SIGNED_IN_FIELD") ??
      "Portal_Last_SignIn__c";
    this.whmcsAccountField =
      this.configService.get<string>("ACCOUNT_WHMCS_FIELD") ?? "WH_Account__c";
    this.personAccountRecordTypeId = this.configService.get<string>(
      "ACCOUNT_PERSON_RECORD_TYPE_ID"
    );
  }

  private readonly portalStatusField: string;
  private readonly portalSourceField: string;
  private readonly portalLastSignedInField: string;
  private readonly whmcsAccountField: string;
  private readonly personAccountRecordTypeId?: string | undefined;

  /**
   * Find Salesforce account by customer number (SF_Account_No__c field)
   * Used during signup and WHMCS linking to verify customer exists
   */
  async findByCustomerNumber(customerNumber: string): Promise<{ id: string } | null> {
    const validCustomerNumber = customerNumberSchema.parse(customerNumber);

    try {
      const result = (await this.connection.query(
        `SELECT Id FROM Account WHERE SF_Account_No__c = '${this.safeSoql(validCustomerNumber)}'`,
        { label: "auth:findAccountByCustomerNumber" }
      )) as SalesforceResponse<SalesforceAccountRecord>;
      return result.totalSize > 0 ? { id: result.records[0]?.Id ?? "" } : null;
    } catch (error) {
      this.logger.error("Failed to find account by customer number", {
        error: extractErrorMessage(error),
      });
      throw new Error("Failed to find account");
    }
  }

  async findWithDetailsByCustomerNumber(customerNumber: string): Promise<{
    id: string;
    Name?: string | null | undefined;
    WH_Account__c?: string | null | undefined;
  } | null> {
    const validCustomerNumber = customerNumberSchema.parse(customerNumber);

    try {
      const result = (await this.connection.query(
        `SELECT Id, Name, WH_Account__c FROM Account WHERE SF_Account_No__c = '${this.safeSoql(validCustomerNumber)}'`,
        { label: "auth:findAccountWithDetails" }
      )) as SalesforceResponse<SalesforceAccountRecord>;

      if (result.totalSize === 0) {
        return null;
      }

      const record = result.records[0];
      return {
        id: record?.Id ?? "",
        Name: record?.Name,
        WH_Account__c: record?.WH_Account__c || undefined,
      };
    } catch (error) {
      this.logger.error("Failed to find account with details by customer number", {
        error: extractErrorMessage(error),
      });
      throw new Error("Failed to find account");
    }
  }

  /**
   * Get account details including WH_Account__c field
   * Used in signup workflow to check if account is already linked to WHMCS
   */
  async getAccountDetails(accountId: string): Promise<{
    id: string;
    WH_Account__c?: string | null | undefined;
    Name?: string | null | undefined;
  } | null> {
    const validAccountId = salesforceIdSchema.parse(accountId);

    try {
      const result = (await this.connection.query(
        `SELECT Id, Name, WH_Account__c FROM Account WHERE Id = '${this.safeSoql(validAccountId)}'`,
        { label: "auth:getAccountDetails" }
      )) as SalesforceResponse<SalesforceAccountRecord>;

      if (result.totalSize === 0) {
        return null;
      }

      const record = result.records[0];
      return {
        id: record?.Id ?? "",
        Name: record?.Name,
        WH_Account__c: record?.WH_Account__c || undefined,
      };
    } catch (error) {
      this.logger.error("Failed to get account details", {
        accountId,
        error: extractErrorMessage(error),
      });
      throw new Error("Failed to get account details");
    }
  }

  /**
   * Escape single quotes for SOQL queries
   * @private
   */
  private safeSoql(input: string): string {
    return input.replace(/'/g, "\\'");
  }

  // ============================================================================
  // Account Creation Methods (for Checkout Registration)
  // ============================================================================

  /**
   * Check if a Salesforce account exists with the given email
   * Used to prevent duplicate account creation during checkout
   */
  async findByEmail(email: string): Promise<{ id: string; accountNumber: string } | null> {
    try {
      // Search for Contact with matching email and get the associated Account
      const result = (await this.connection.query(
        `SELECT Account.Id, Account.SF_Account_No__c FROM Contact WHERE Email = '${this.safeSoql(email)}' LIMIT 1`,
        { label: "checkout:findAccountByEmail" }
      )) as SalesforceResponse<{ Account: { Id: string; SF_Account_No__c: string } }>;

      if (result.totalSize > 0 && result.records[0]?.Account) {
        return {
          id: result.records[0].Account.Id,
          accountNumber: result.records[0].Account.SF_Account_No__c,
        };
      }

      return null;
    } catch (error) {
      this.logger.error("Failed to find account by email", {
        error: extractErrorMessage(error),
      });
      return null;
    }
  }

  /**
   * Create a new Salesforce Account for a new customer
   * Used when customer signs up through checkout (no existing sfNumber)
   *
   * @returns The created account ID and auto-generated account number
   */
  async createAccount(
    data: CreateSalesforceAccountRequest
  ): Promise<{ accountId: string; accountNumber: string }> {
    this.logger.log("Creating new Salesforce Account", { email: data.email });

    const personAccountRecordTypeId = await this.resolvePersonAccountRecordTypeId();

    const accountPayload: Record<string, unknown> = {
      // Person Account fields (required for Person Accounts)
      FirstName: data.firstName,
      LastName: data.lastName,
      PersonEmail: data.email,
      PersonMobilePhone: data.phone,
      // Record type for Person Accounts (required)
      RecordTypeId: personAccountRecordTypeId,
      // Portal tracking fields
      [this.portalStatusField]: data.portalStatus ?? "Not Yet",
      [this.portalSourceField]: data.portalSource,
    };

    this.logger.debug("Person Account creation payload", {
      recordTypeId: personAccountRecordTypeId,
      hasFirstName: Boolean(accountPayload["FirstName"]),
      hasLastName: Boolean(accountPayload["LastName"]),
      hasPersonEmail: Boolean(accountPayload["PersonEmail"]),
    });

    try {
      const createMethod = this.connection.sobject("Account").create;
      if (!createMethod) {
        throw new Error("Salesforce create method not available");
      }

      const result = await createMethod(accountPayload);

      if (!result || typeof result !== "object" || !("id" in result)) {
        throw new Error("Salesforce Account creation failed - no ID returned");
      }

      const accountId = result.id as string;

      // Query back the auto-generated account number
      const accountRecord = (await this.connection.query(
        `SELECT SF_Account_No__c FROM Account WHERE Id = '${this.safeSoql(accountId)}'`,
        { label: "checkout:getCreatedAccountNumber" }
      )) as SalesforceResponse<{ SF_Account_No__c: string }>;

      const accountNumber = accountRecord.records[0]?.SF_Account_No__c || "";

      if (!accountNumber) {
        this.logger.warn("Account number not found for newly created account", { accountId });
      }

      this.logger.log("Salesforce Account created", {
        accountId,
        accountNumber,
      });

      return {
        accountId,
        accountNumber,
      };
    } catch (error) {
      // Log full error details for debugging
      const errorMessage = extractErrorMessage(error);
      const errorDetails: Record<string, unknown> = {
        error: errorMessage,
        email: data.email,
        payload: accountPayload,
      };

      // Capture jsforce error details if available
      if (error && typeof error === "object") {
        const err = error as Record<string, unknown>;
        // jsforce errors often have these properties
        if (err["errorCode"]) errorDetails["errorCode"] = err["errorCode"];
        if (err["fields"]) errorDetails["fields"] = err["fields"];
        if (err["message"]) errorDetails["rawMessage"] = err["message"];
        // Check for nested error objects
        if (err["error"]) errorDetails["nestedError"] = err["error"];
        // Log all enumerable properties in development
        if (process.env["NODE_ENV"] !== "production") {
          errorDetails["allProperties"] = Object.keys(err);
          try {
            errorDetails["fullError"] = JSON.stringify(error, null, 2);
          } catch {
            errorDetails["fullError"] = errorMessage;
          }
        }
      }

      // Log with high visibility
      this.logger.error(
        {
          ...errorDetails,
          msg: `Failed to create Salesforce Account: ${errorMessage}`,
        },
        "Failed to create Salesforce Account"
      );
      throw new Error("Failed to create customer account in CRM");
    }
  }

  private async resolvePersonAccountRecordTypeId(): Promise<string> {
    if (this.personAccountRecordTypeId) {
      const recordTypeId = salesforceIdSchema.parse(this.personAccountRecordTypeId);
      this.logger.debug("Using configured Person Account RecordType", {
        recordTypeId,
      });
      return recordTypeId;
    }

    try {
      const recordTypeQuery = (await this.connection.query(
        `SELECT Id, Name FROM RecordType WHERE SObjectType = 'Account' AND IsPersonType = true LIMIT 1`,
        { label: "signup:getPersonAccountRecordType" }
      )) as SalesforceResponse<{ Id: string; Name: string }>;

      if (recordTypeQuery.totalSize === 0) {
        throw new Error(
          "No Person Account record type found. Person Accounts may not be enabled in this Salesforce org."
        );
      }

      const record = recordTypeQuery.records[0];
      if (!record) {
        throw new Error("Person Account RecordType record not found");
      }
      const recordTypeId = record.Id;
      this.logger.debug("Found Person Account RecordType", {
        recordTypeId,
        name: record.Name,
      });
      return recordTypeId;
    } catch (error) {
      this.logger.error("Failed to query Person Account RecordType", {
        error: extractErrorMessage(error),
      });
      throw new Error("Failed to determine Person Account record type");
    }
  }

  /**
   * Update PersonContact for Person Account with additional fields
   * For Person Accounts, the Contact is automatically created, so we update it instead
   */
  async createContact(data: CreateSalesforceContactRequest): Promise<{ contactId: string }> {
    this.logger.log("Updating PersonContact for Person Account", {
      accountId: data.accountId,
      email: data.email,
    });

    // Map gender values to Salesforce picklist values
    const mapGenderToSalesforce = (gender: "male" | "female" | "other"): string => {
      const mapping: Record<"male" | "female" | "other", string> = {
        male: "男性",
        female: "女性",
        other: "Other",
      };
      return mapping[gender];
    };

    // For Person Accounts, we need to get the PersonContactId from the Account
    // and then update that Contact record
    try {
      // Query the Account to get the PersonContactId
      const accountRecord = (await this.connection.query(
        `SELECT PersonContactId FROM Account WHERE Id = '${this.safeSoql(data.accountId)}'`,
        { label: "signup:getPersonContactId" }
      )) as SalesforceResponse<{ PersonContactId: string }>;

      const personContactId = accountRecord.records[0]?.PersonContactId;
      if (!personContactId) {
        throw new Error("PersonContactId not found for Person Account");
      }

      // Update the PersonContact with additional fields
      const contactPayload: Record<string, unknown> = {
        Id: personContactId,
        MobilePhone: data.phone,
        Sex__c: mapGenderToSalesforce(data.gender),
        Birthdate: data.dateOfBirth, // Salesforce expects YYYY-MM-DD format
      };

      const updateMethod = this.connection.sobject("Contact").update;
      if (!updateMethod) {
        throw new Error("Salesforce update method not available");
      }

      await updateMethod(contactPayload as Record<string, unknown> & { Id: string });

      this.logger.log("PersonContact updated", { contactId: personContactId });
      return { contactId: personContactId };
    } catch (error) {
      this.logger.error("Failed to update PersonContact", {
        error: extractErrorMessage(error),
        accountId: data.accountId,
      });
      throw new Error("Failed to update customer contact in CRM");
    }
  }

  // ============================================================================
  // Contact Address Update Methods
  // ============================================================================

  /**
   * Update PersonContact address fields for a Person Account
   * Used for storing Japanese address data in Salesforce
   *
   * @param accountId - Salesforce Account ID
   * @param address - Japanese address data
   */
  async updateContactAddress(
    accountId: string,
    address: UpdateSalesforceContactAddressRequest
  ): Promise<void> {
    const validAccountId = salesforceIdSchema.parse(accountId);

    this.logger.debug("Updating PersonContact address", {
      accountId: validAccountId,
      hasMailingStreet: Boolean(address.mailingStreet),
      hasMailingCity: Boolean(address.mailingCity),
    });

    try {
      // For Person Accounts, we need to get the PersonContactId from the Account
      const accountRecord = (await this.connection.query(
        `SELECT PersonContactId FROM Account WHERE Id = '${this.safeSoql(validAccountId)}'`,
        { label: "address:getPersonContactId" }
      )) as SalesforceResponse<{ PersonContactId: string }>;

      const personContactId = accountRecord.records[0]?.PersonContactId;
      if (!personContactId) {
        this.logger.warn("PersonContactId not found for Person Account", {
          accountId: validAccountId,
        });
        return;
      }

      // Build contact update payload with Japanese mailing address fields
      const contactPayload: Record<string, unknown> = {
        Id: personContactId,
        MailingStreet: address.mailingStreet || "",
        MailingCity: address.mailingCity,
        MailingState: address.mailingState,
        MailingPostalCode: address.mailingPostalCode,
        MailingCountry: address.mailingCountry || "Japan",
      };

      // Add building name and room number custom fields (on Contact)
      if (address.buildingName !== undefined) {
        contactPayload["BuildingName__c"] = address.buildingName;
      }
      if (address.roomNumber !== undefined) {
        contactPayload["RoomNumber__c"] = address.roomNumber;
      }

      const updateMethod = this.connection.sobject("Contact").update;
      if (!updateMethod) {
        this.logger.warn("Salesforce update method not available");
        return;
      }

      await updateMethod(contactPayload as Record<string, unknown> & { Id: string });

      this.logger.log("PersonContact address updated successfully", {
        accountId: validAccountId,
        contactId: personContactId,
      });
    } catch (error) {
      // Log and re-throw - caller needs to handle failures (e.g., queue reconciliation)
      // WHMCS is source of truth, but caller decides how to handle SF failures
      this.logger.error("Failed to update PersonContact address", {
        accountId: validAccountId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  // ============================================================================
  // Portal Field Update Methods
  // ============================================================================

  async updatePortalFields(
    accountId: string,
    update: SalesforceAccountPortalUpdate
  ): Promise<void> {
    const validAccountId = salesforceIdSchema.parse(accountId);
    const payload: Record<string, unknown> = { Id: validAccountId };

    if (update.status) {
      payload[this.portalStatusField] = update.status;
    }

    if (update.source) {
      payload[this.portalSourceField] = update.source;
    }

    if (update.lastSignedInAt) {
      payload[this.portalLastSignedInField] = update.lastSignedInAt.toISOString();
    }

    if (update.whmcsAccountId !== undefined) {
      payload[this.whmcsAccountField] =
        update.whmcsAccountId === null ? null : String(update.whmcsAccountId);
    }

    if (Object.keys(payload).length <= 1) {
      return;
    }

    try {
      const updateMethod = this.connection.sobject("Account").update;
      if (!updateMethod) {
        this.logger.warn("Salesforce update method not available");
        return;
      }

      await updateMethod(payload as Record<string, unknown> & { Id: string });
      this.logger.debug("Updated Salesforce account portal fields", {
        accountId: validAccountId,
        hasStatus: Boolean(update.status),
        hasSource: Boolean(update.source),
        hasLastSignedIn: Boolean(update.lastSignedInAt),
      });
    } catch (error) {
      this.logger.warn("Failed to update account portal fields", {
        accountId: validAccountId,
        error: extractErrorMessage(error),
      });
    }
  }
}

export interface SalesforceAccountPortalUpdate {
  status?: string;
  source?: string;
  lastSignedInAt?: Date;
  whmcsAccountId?: string | number | null;
}

/**
 * Request type for creating a new Salesforce Account
 */
export interface CreateSalesforceAccountRequest {
  firstName: string;
  lastName: string;
  email: string;
  phone: string;
  /** Portal status - defaults to "Not Yet" if not provided */
  portalStatus?: string;
  /** Portal registration source - REQUIRED, must be explicitly set by caller */
  portalSource: string;
}

/**
 * Request type for creating a new Salesforce Contact
 */
export interface CreateSalesforceContactRequest {
  accountId: string;
  firstName: string;
  lastName: string;
  email: string;
  phone: string;
  gender: "male" | "female" | "other";
  dateOfBirth: string; // YYYY-MM-DD format
}

/**
 * Request type for updating Salesforce Contact address
 * Used for dual-write: English to WHMCS, Japanese to Salesforce
 */
export interface UpdateSalesforceContactAddressRequest {
  /** Japanese street (町名/番地) */
  mailingStreet: string;
  /** Japanese city */
  mailingCity: string;
  /** Japanese prefecture */
  mailingState: string;
  /** ZIP code (postal code) */
  mailingPostalCode: string;
  /** Country (always Japan) */
  mailingCountry?: string;
  /** Building name */
  buildingName?: string | null;
  /** Room number */
  roomNumber?: string | null;
}
