import { Injectable, Inject, Optional } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { FreebitOperationException } from "@bff/core/exceptions/domain-exceptions.js";
import type {
  FreebitAccountDetailsResponse,
  FreebitTrafficInfoResponse,
  FreebitQuotaHistoryResponse,
  SimDetails,
  SimUsage,
  SimTopUpHistory,
} from "../interfaces/freebit.types.js";
import type { VoiceOptionsService } from "@bff/modules/voice-options/services/voice-options.service.js";

const NOT_IN_API_RESPONSE = "(not in API response)";

@Injectable()
export class FreebitMapperService {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    @Optional()
    @Inject("SimVoiceOptionsService")
    private readonly voiceOptionsService?: VoiceOptionsService
  ) {}

  private parseOptionFlag(value: unknown, defaultValue: boolean = false): boolean {
    // If value is undefined or null, return the default
    if (value === undefined || value === null) {
      return defaultValue;
    }

    if (typeof value === "boolean") {
      return value;
    }
    if (typeof value === "number") {
      return value === 10 || value === 1;
    }
    if (typeof value === "string") {
      const normalized = value.trim().toLowerCase();
      if (normalized === "on" || normalized === "true") {
        return true;
      }
      if (normalized === "off" || normalized === "false") {
        return false;
      }
      const numeric = Number(normalized);
      if (!Number.isNaN(numeric)) {
        return numeric === 10 || numeric === 1;
      }
    }
    return defaultValue;
  }

  /**
   * Map SIM status from Freebit API to domain status
   */
  mapSimStatus(status: string): "active" | "suspended" | "cancelled" | "pending" {
    switch (status) {
      case "active":
        return "active";
      case "suspended":
        return "suspended";
      case "temporary":
      case "waiting":
        return "pending";
      case "obsolete":
        return "cancelled";
      default:
        return "pending";
    }
  }

  /**
   * Map Freebit account details response to SimDetails
   */
  async mapToSimDetails(response: FreebitAccountDetailsResponse): Promise<SimDetails> {
    const account = response.responseDatas[0];
    if (!account) {
      throw new FreebitOperationException("No account data in response");
    }

    // Debug: Log raw voice option fields from API response
    this.logger.debug("[FreebitMapper] Raw API voice option fields", {
      account: account.account,
      voicemail: account.voicemail,
      voiceMail: account.voiceMail,
      callwaiting: account.callwaiting,
      callWaiting: account.callWaiting,
      worldwing: account.worldwing,
      worldWing: account.worldWing,
      talk: account.talk,
      sms: account.sms,
    });

    let simType: "standard" | "nano" | "micro" | "esim" = "standard";
    if (account.eid) {
      simType = "esim";
    } else if (account.simSize) {
      simType = account.simSize;
    }

    // Try to get voice options from database first
    // Default to false - show as disabled unless API confirms enabled
    let voiceMailEnabled = false;
    let callWaitingEnabled = false;
    let internationalRoamingEnabled = false;
    let networkType = String(account.networkType ?? account.contractLine ?? "4G");

    // Try to load stored options from database first
    let storedOptions = null;
    if (this.voiceOptionsService) {
      try {
        storedOptions = await this.voiceOptionsService.getVoiceOptions(
          String(account.account ?? "")
        );
      } catch (error) {
        this.logger.warn("[FreebitMapper] Failed to load voice options from database", {
          account: account.account,
          error,
        });
      }
    }

    if (storedOptions) {
      // Use stored options from database
      voiceMailEnabled = storedOptions.voiceMailEnabled;
      callWaitingEnabled = storedOptions.callWaitingEnabled;
      internationalRoamingEnabled = storedOptions.internationalRoamingEnabled;
      networkType = storedOptions.networkType;

      this.logger.debug("[FreebitMapper] Loaded voice options from database", {
        account: account.account,
        options: storedOptions,
      });
    } else {
      // No stored options, parse from API response
      // Default to false - disabled unless API explicitly returns 10 (enabled)
      voiceMailEnabled = this.parseOptionFlag(account.voicemail ?? account.voiceMail, false);
      callWaitingEnabled = this.parseOptionFlag(account.callwaiting ?? account.callWaiting, false);
      internationalRoamingEnabled = this.parseOptionFlag(
        account.worldwing ?? account.worldWing,
        false
      );

      this.logger.debug(
        "[FreebitMapper] No stored options found, using API values (default: disabled)",
        {
          account: account.account,
          rawVoiceMail: account.voicemail ?? account.voiceMail ?? NOT_IN_API_RESPONSE,
          rawCallWaiting: account.callwaiting ?? account.callWaiting ?? NOT_IN_API_RESPONSE,
          rawWorldWing: account.worldwing ?? account.worldWing ?? NOT_IN_API_RESPONSE,
          parsedVoiceMailEnabled: voiceMailEnabled,
          parsedCallWaitingEnabled: callWaitingEnabled,
          parsedInternationalRoamingEnabled: internationalRoamingEnabled,
        }
      );
    }

    // Convert quota from KB to MB if needed
    // Freebit API returns quota in KB, but remainingQuotaMb should be in MB
    let remainingQuotaMb = 0;
    let remainingQuotaKb = 0;

    if (account.remainingQuotaMb != null) {
      // If API explicitly provides remainingQuotaMb, use it directly
      remainingQuotaMb = Number(account.remainingQuotaMb);
      remainingQuotaKb = Number(account.remainingQuotaKb ?? remainingQuotaMb * 1024);
    } else if (account.quota != null) {
      // If only quota is provided, it's in KB - convert to MB (1 MB = 1024 KB)
      remainingQuotaKb = Number(account.quota);
      remainingQuotaMb = remainingQuotaKb / 1024;
    } else if (account.remainingQuotaKb != null) {
      // If only remainingQuotaKb is provided, convert to MB (1 MB = 1024 KB)
      remainingQuotaKb = Number(account.remainingQuotaKb);
      remainingQuotaMb = remainingQuotaKb / 1024;
    }

    // Log raw account data in dev to debug MSISDN availability
    if (process.env["NODE_ENV"] !== "production") {
      this.logger.debug("[FREEBIT ACCOUNT DATA]", {
        account: account.account,
        msisdn: account.msisdn,
        eid: account.eid,
        iccid: account.iccid,
      });
    }

    return {
      account: String(account.account ?? ""),
      status: this.mapSimStatus(String(account.state ?? account.status ?? "pending")),
      planCode: String(account.planCode ?? ""),
      planName: String(account.planName ?? ""),
      simType,
      iccid: String(account.iccid ?? ""),
      eid: String(account.eid ?? ""),
      msisdn: String(account.msisdn ?? account.account ?? ""),
      imsi: String(account.imsi ?? ""),
      remainingQuotaMb,
      remainingQuotaKb,
      voiceMailEnabled,
      callWaitingEnabled,
      internationalRoamingEnabled,
      networkType,
      activatedAt: account.startDate ? String(account.startDate) : undefined,
      expiresAt: account.async ? String(account.async.date) : undefined,
      ipv4: account.ipv4,
      ipv6: account.ipv6,
      startDate: account.startDate ? String(account.startDate) : undefined,
      hasVoice: account.talk === 10,
      hasSms: account.sms === 10,
    };
  }

  /**
   * Map Freebit traffic info response to SimUsage
   */
  mapToSimUsage(response: FreebitTrafficInfoResponse): SimUsage {
    if (!response.traffic) {
      throw new FreebitOperationException("No traffic data in response");
    }

    const todayUsageKb = Number.parseInt(response.traffic.today, 10) || 0;
    const recentDaysData = response.traffic.inRecentDays.split(",").map((usage, index) => ({
      date:
        new Date(Date.now() - (index + 1) * 24 * 60 * 60 * 1000).toISOString().split("T")[0] ?? "",
      usageKb: Number.parseInt(usage, 10) || 0,
      usageMb: Math.round(((Number.parseInt(usage, 10) || 0) / 1024) * 100) / 100,
    }));

    return {
      account: String(response.account ?? ""),
      todayUsageMb: Math.round((todayUsageKb / 1024) * 100) / 100,
      todayUsageKb,
      recentDaysUsage: recentDaysData,
      isBlacklisted: response.traffic.blackList === "10",
    };
  }

  /**
   * Map Freebit quota history response to SimTopUpHistory
   */
  mapToSimTopUpHistory(response: FreebitQuotaHistoryResponse, account: string): SimTopUpHistory {
    if (!response.quotaHistory) {
      throw new FreebitOperationException("No history data in response");
    }

    return {
      account,
      totalAdditions: Number(response.total) || 0,
      additionCount: Number(response.count) || 0,
      history: response.quotaHistory.map(item => ({
        quotaKb: Number.parseInt(item.quota, 10),
        quotaMb: Math.round((Number.parseInt(item.quota, 10) / 1024) * 100) / 100,
        addedDate: item.date,
        expiryDate: item.expire,
        campaignCode: item.quotaCode,
      })),
    };
  }

  /**
   * Normalize account identifier (remove formatting)
   */
  normalizeAccount(account: string): string {
    return account.replace(/[-\s()]/g, "");
  }

  /**
   * Validate account format
   */
  validateAccount(account: string): boolean {
    const normalized = this.normalizeAccount(account);
    // Basic validation - should be digits, typically 10-11 digits for Japanese phone numbers
    return /^\d{10,11}$/.test(normalized);
  }

  /**
   * Format date for Freebit API (YYYYMMDD)
   */
  formatDateForApi(date: Date): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}${month}${day}`;
  }

  /**
   * Parse date from Freebit API format (YYYYMMDD)
   */
  parseDateFromApi(dateString: string): Date | null {
    if (!/^\d{8}$/.test(dateString)) {
      return null;
    }

    const year = Number.parseInt(dateString.slice(0, 4), 10);
    const month = Number.parseInt(dateString.slice(4, 6), 10) - 1; // Month is 0-indexed
    const day = Number.parseInt(dateString.slice(6, 8), 10);

    return new Date(year, month, day);
  }
}
