/**
 * Japan Post Address Service
 *
 * Address lookup service using Japan Post API.
 * Transforms raw API responses to domain types using domain mappers.
 */

import {
  Injectable,
  Inject,
  BadRequestException,
  ServiceUnavailableException,
} from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { JapanPostConnectionService } from "./japanpost-connection.service.js";
import { JapanPost } from "@customer-portal/domain/address/providers";
import type { AddressLookupResult } from "@customer-portal/domain/address";

@Injectable()
export class JapanPostAddressService {
  constructor(
    private readonly connection: JapanPostConnectionService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Lookup address by ZIP code
   *
   * @param zipCode - ZIP code (with or without hyphen, e.g., "100-0001" or "1000001")
   * @param clientIp - Client IP address for API request (defaults to 127.0.0.1)
   * @returns Domain AddressLookupResult with Japanese and romanized address data
   * @throws BadRequestException if ZIP code format is invalid
   * @throws ServiceUnavailableException if Japan Post API is unavailable
   */
  async lookupByZipCode(
    zipCode: string,
    clientIp: string = "127.0.0.1"
  ): Promise<AddressLookupResult> {
    // Normalize ZIP code (remove hyphen)
    const normalizedZip = zipCode.replace(/-/g, "");

    // Validate format
    if (!/^\d{7}$/.test(normalizedZip)) {
      this.logger.warn("Invalid ZIP code format", {
        zipCode,
        normalizedZip,
        reason: "Must be exactly 7 digits",
      });
      throw new BadRequestException("ZIP code must be 7 digits (e.g., 100-0001)");
    }

    // Check if service is configured
    if (!this.connection.isConfigured()) {
      this.logger.error("Japan Post API not configured - address lookup unavailable", {
        zipCode: normalizedZip,
        configErrors: this.connection.getConfigErrors(),
      });
      throw new ServiceUnavailableException("Address lookup service is not available");
    }

    const startTime = Date.now();

    let rawResponse: unknown;

    try {
      rawResponse = await this.connection.searchByZipCode(normalizedZip, clientIp);

      // Check if response is an API error (returned with 200 status)
      if (this.isApiErrorResponse(rawResponse)) {
        const errorResponse = rawResponse as {
          error_code?: string;
          message?: string;
          request_id?: string;
        };
        this.logger.error("Japan Post API returned error in response body", {
          zipCode: normalizedZip,
          durationMs: Date.now() - startTime,
          errorCode: errorResponse.error_code,
          message: errorResponse.message,
          requestId: errorResponse.request_id,
        });
        throw new ServiceUnavailableException("Address lookup service error. Please try again.");
      }

      // Use domain mapper for transformation (single transformation point)
      const result = JapanPost.transformJapanPostSearchResponse(rawResponse);

      this.logger.log("Address lookup completed", {
        zipCode: normalizedZip,
        found: result.count > 0,
        count: result.count,
        durationMs: Date.now() - startTime,
      });

      return result;
    } catch (error) {
      const durationMs = Date.now() - startTime;

      // Re-throw known exceptions (already logged at connection layer)
      if (error instanceof BadRequestException || error instanceof ServiceUnavailableException) {
        throw error;
      }

      // Get raw error message for classification
      const rawMessage = error instanceof Error ? error.message : String(error);
      const errorMessage = extractErrorMessage(error);

      // Check if this is an HTTP/connection error (already logged at connection layer)
      const isConnectionError =
        rawMessage.includes("HTTP") ||
        rawMessage.includes("timed out") ||
        rawMessage.includes("failed") ||
        errorMessage.includes("HTTP") ||
        errorMessage.includes("timed out");

      // Check if this is a Zod validation error
      const isZodError = error instanceof Error && error.name === "ZodError";

      if (!isConnectionError) {
        // Only log unexpected errors (e.g., transformation failures)
        // Include raw response for debugging schema mismatches
        this.logger.error("Address lookup transformation error", {
          zipCode: normalizedZip,
          durationMs,
          errorType: error instanceof Error ? error.constructor.name : "Unknown",
          errorName: error instanceof Error ? error.name : "Unknown",
          isZodError,
          error: errorMessage,
          // For Zod errors, show the full error for debugging
          zodIssues: isZodError
            ? JSON.stringify((error as { issues?: unknown }).issues)
            : undefined,
          // Log raw response to debug schema mismatch (truncated for safety)
          rawResponse: rawResponse
            ? JSON.stringify(rawResponse).slice(0, 500)
            : "undefined (error before API response)",
        });
      }

      throw new ServiceUnavailableException("Failed to lookup address. Please try again.");
    }
  }

  /**
   * Check if the Japan Post service is available
   */
  isAvailable(): boolean {
    return this.connection.isConfigured();
  }

  /**
   * Check if response is an API error (has error_code field instead of addresses)
   */
  private isApiErrorResponse(response: unknown): boolean {
    if (!response || typeof response !== "object") {
      return false;
    }
    const obj = response as Record<string, unknown>;
    // API error responses have error_code field, valid responses have addresses array
    return "error_code" in obj || ("message" in obj && !("addresses" in obj));
  }
}
