/**
 * Address Writer Service
 *
 * Centralizes bilingual address handling for registration workflows.
 * Writes Japanese address to Salesforce and English address to WHMCS.
 *
 * - SF: Uses Japanese fields (prefectureJa, cityJa, townJa) directly from bilingual address
 * - WHMCS: Resolves English fields by looking up postal code via Japan Post API
 */

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

import {
  type BilingualAddress,
  type WhmcsAddressFields,
  prepareSalesforceContactAddressFields,
  prepareWhmcsAddressFields,
} from "@customer-portal/domain/address";

import { JapanPostFacade } from "@bff/integrations/japanpost/index.js";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";

export interface ResolveWhmcsAddressParams {
  postcode: string;
  /** Japanese town name for disambiguation when postal code returns multiple results */
  townJa?: string | undefined;
  streetAddress: string;
  buildingName?: string | null | undefined;
  roomNumber?: string | null | undefined;
  residenceType: "house" | "apartment";
}

@Injectable()
export class AddressWriterService {
  /** In-flight Japan Post lookups keyed by postcode — deduplicates concurrent calls */
  private readonly inflightLookups = new Map<
    string,
    Promise<Awaited<ReturnType<JapanPostFacade["lookupByZipCode"]>>>
  >();

  constructor(
    private readonly japanPost: JapanPostFacade,
    private readonly salesforceFacade: SalesforceFacade,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Write Japanese address to Salesforce Contact.
   * Uses prepareSalesforceContactAddressFields() for field mapping.
   */
  async writeToSalesforce(sfAccountId: string, address: BilingualAddress): Promise<void> {
    const sfFields = prepareSalesforceContactAddressFields(address);

    await this.salesforceFacade.updateContactAddress(sfAccountId, {
      mailingStreet: sfFields.MailingStreet,
      mailingCity: sfFields.MailingCity,
      mailingState: sfFields.MailingState,
      mailingPostalCode: sfFields.MailingPostalCode,
      mailingCountry: sfFields.MailingCountry,
      buildingName: sfFields.BuildingName__c,
      roomNumber: sfFields.RoomNumber__c,
    });

    this.logger.debug({ sfAccountId }, "Wrote Japanese address to Salesforce");
  }

  /**
   * Resolve English address from postal code via Japan Post API
   * and prepare WHMCS address fields.
   *
   * Uses in-flight deduplication so concurrent lookups for the same
   * postcode share a single API call.
   */
  async resolveWhmcsAddress(params: ResolveWhmcsAddressParams): Promise<WhmcsAddressFields> {
    const { postcode, townJa, streetAddress, buildingName, roomNumber, residenceType } = params;

    const lookupResult = await this.lookupPostalCode(postcode);

    if (lookupResult.addresses.length === 0) {
      this.logger.warn({ postcode }, "Japan Post API returned no results for postal code");
      throw new Error(`No address found for postal code ${postcode}`);
    }

    // Find the matching address entry
    let matched = lookupResult.addresses[0]!;

    if (townJa && lookupResult.addresses.length > 1) {
      const townMatch = lookupResult.addresses.find(a => a.town === townJa);
      if (townMatch) {
        matched = townMatch;
      } else {
        this.logger.warn(
          { postcode, townJa, availableTowns: lookupResult.addresses.map(a => a.town) },
          "Could not match townJa in Japan Post results, using first result"
        );
      }
    }

    // Build a BilingualAddress from resolved data
    const bilingualAddress: BilingualAddress = {
      postcode,
      prefecture: matched.prefectureRoma,
      city: matched.cityRoma,
      town: matched.townRoma,
      prefectureJa: matched.prefecture,
      cityJa: matched.city,
      townJa: matched.town,
      streetAddress,
      buildingName: buildingName ?? null,
      roomNumber: roomNumber ?? null,
      residenceType,
    };

    return prepareWhmcsAddressFields(bilingualAddress);
  }

  /**
   * Deduplicated Japan Post lookup — concurrent calls for the same postcode
   * share one API request.
   */
  private async lookupPostalCode(
    postcode: string
  ): Promise<Awaited<ReturnType<JapanPostFacade["lookupByZipCode"]>>> {
    const inflight = this.inflightLookups.get(postcode);
    if (inflight) {
      return inflight;
    }

    const promise = this.japanPost.lookupByZipCode(postcode);
    this.inflightLookups.set(postcode, promise);

    try {
      return await promise;
    } finally {
      this.inflightLookups.delete(postcode);
    }
  }

  /**
   * Prepare WHMCS address fields from a full BilingualAddress.
   * Uses the already-populated English fields — no API call needed.
   */
  resolveWhmcsAddressFromBilingual(address: BilingualAddress): WhmcsAddressFields {
    return prepareWhmcsAddressFields(address);
  }

  /**
   * Convert WhmcsAddressFields to the shape expected by CreateWhmcsClientStep.
   */
  toWhmcsStepAddress(fields: WhmcsAddressFields): {
    address1: string;
    address2?: string;
    city: string;
    state: string;
    postcode: string;
    country: string;
  } {
    return {
      address1: fields.address1 || "",
      ...(fields.address2 && { address2: fields.address2 }),
      city: fields.city,
      state: fields.state,
      postcode: fields.postcode,
      country: fields.country,
    };
  }

  /**
   * Build case address from bilingual address.
   * Maps Japanese fields to the eligibility case step's expected format.
   */
  toCaseAddress(address: BilingualAddress): {
    address1: string;
    city: string;
    state: string;
    postcode: string;
    country: string;
  } {
    return {
      address1: `${address.townJa}${address.streetAddress}`,
      city: address.cityJa,
      state: address.prefectureJa,
      postcode: address.postcode,
      country: "Japan",
    };
  }
}
