import { Injectable, Inject, BadRequestException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { getErrorMessage } from "@bff/core/utils/error.util";
import { FreebitClientService } from "./freebit-client.service";
import { FreebitMapperService } from "./freebit-mapper.service";
import { FreebitAuthService } from "./freebit-auth.service";
import type {
  FreebitAccountDetailsRequest,
  FreebitAccountDetailsResponse,
  FreebitTrafficInfoRequest,
  FreebitTrafficInfoResponse,
  FreebitTopUpRequest,
  FreebitTopUpResponse,
  FreebitQuotaHistoryRequest,
  FreebitQuotaHistoryResponse,
  FreebitPlanChangeRequest,
  FreebitPlanChangeResponse,
  FreebitContractLineChangeRequest,
  FreebitContractLineChangeResponse,
  FreebitAddSpecRequest,
  FreebitAddSpecResponse,
  FreebitVoiceOptionSettings,
  FreebitVoiceOptionRequest,
  FreebitVoiceOptionResponse,
  FreebitCancelPlanRequest,
  FreebitCancelPlanResponse,
  FreebitEsimReissueRequest,
  FreebitEsimReissueResponse,
  FreebitEsimAddAccountRequest,
  FreebitEsimAddAccountResponse,
  FreebitEsimAccountActivationRequest,
  FreebitEsimAccountActivationResponse,
  SimDetails,
  SimUsage,
  SimTopUpHistory,
} from "../interfaces/freebit.types";

@Injectable()
export class FreebitOperationsService {
  constructor(
    private readonly client: FreebitClientService,
    private readonly mapper: FreebitMapperService,
    private readonly auth: FreebitAuthService,
    @Inject(Logger) private readonly logger: Logger,
    @Inject("SimVoiceOptionsService") private readonly voiceOptionsService?: any
  ) {}

  private readonly operationTimestamps = new Map<
    string,
    {
      voice?: number;
      network?: number;
      plan?: number;
      cancellation?: number;
    }
  >();

  private getOperationWindow(account: string) {
    if (!this.operationTimestamps.has(account)) {
      this.operationTimestamps.set(account, {});
    }
    return this.operationTimestamps.get(account)!;
  }

  private assertOperationSpacing(account: string, op: "voice" | "network" | "plan") {
    const windowMs = 30 * 60 * 1000;
    const now = Date.now();
    const entry = this.getOperationWindow(account);

    if (op === "voice") {
      if (entry.plan && now - entry.plan < windowMs) {
        throw new BadRequestException(
          "Voice feature changes must be at least 30 minutes apart from plan changes. Please try again later."
        );
      }
      if (entry.network && now - entry.network < windowMs) {
        throw new BadRequestException(
          "Voice feature changes must be at least 30 minutes apart from network type updates. Please try again later."
        );
      }
    }

    if (op === "network") {
      if (entry.voice && now - entry.voice < windowMs) {
        throw new BadRequestException(
          "Network type updates must be requested 30 minutes after voice option changes. Please try again later."
        );
      }
      if (entry.plan && now - entry.plan < windowMs) {
        throw new BadRequestException(
          "Network type updates must be requested at least 30 minutes apart from plan changes. Please try again later."
        );
      }
    }

    if (op === "plan") {
      if (entry.voice && now - entry.voice < windowMs) {
        throw new BadRequestException(
          "Plan changes must be requested 30 minutes after voice option changes. Please try again later."
        );
      }
      if (entry.network && now - entry.network < windowMs) {
        throw new BadRequestException(
          "Plan changes must be requested 30 minutes after network type updates. Please try again later."
        );
      }
      if (entry.cancellation) {
        throw new BadRequestException(
          "This subscription has a pending cancellation. Plan changes are no longer permitted."
        );
      }
    }
  }

  private stampOperation(account: string, op: "voice" | "network" | "plan" | "cancellation") {
    const entry = this.getOperationWindow(account);
    entry[op] = Date.now();
  }

  /**
   * Get SIM account details with endpoint fallback
   */
  async getSimDetails(account: string): Promise<SimDetails> {
    try {
      const request: Omit<FreebitAccountDetailsRequest, "authKey"> = {
        version: "2",
        requestDatas: [{ kind: "MVNO", account }],
      };

      const config = this.auth.getConfig();
      const configured = config.detailsEndpoint || "/master/getAcnt/";
      const candidates = Array.from(
        new Set([
          configured,
          configured.replace(/\/$/, ""),
          "/master/getAcnt/",
          "/master/getAcnt",
          "/mvno/getAccountDetail/",
          "/mvno/getAccountDetail",
          "/mvno/getAcntDetail/",
          "/mvno/getAcntDetail",
          "/mvno/getAccountInfo/",
          "/mvno/getAccountInfo",
          "/mvno/getSubscriberInfo/",
          "/mvno/getSubscriberInfo",
          "/mvno/getInfo/",
          "/mvno/getInfo",
          "/master/getDetail/",
          "/master/getDetail",
        ])
      );

      let response: FreebitAccountDetailsResponse | undefined;
      let lastError: unknown;

      for (const ep of candidates) {
        try {
          if (ep !== candidates[0]) {
            this.logger.warn(`Retrying Freebit account details with alternative endpoint: ${ep}`);
          }
          response = await this.client.makeAuthenticatedRequest<
            FreebitAccountDetailsResponse,
            typeof request
          >(ep, request);
          break;
        } catch (err: unknown) {
          lastError = err;
          if (getErrorMessage(err).includes("HTTP 404")) {
            continue; // try next endpoint
          }
        }
      }

      if (!response) {
        if (lastError instanceof Error) {
          throw lastError;
        }
        throw new Error("Failed to get SIM details from any endpoint");
      }

      return await this.mapper.mapToSimDetails(response);
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to get SIM details for account ${account}`, {
        account,
        error: message,
      });
      throw new BadRequestException(`Failed to get SIM details: ${message}`);
    }
  }

  /**
   * Get SIM usage/traffic information
   */
  async getSimUsage(account: string): Promise<SimUsage> {
    try {
      const request: Omit<FreebitTrafficInfoRequest, "authKey"> = { account };

      const response = await this.client.makeAuthenticatedRequest<
        FreebitTrafficInfoResponse,
        typeof request
      >("/mvno/getTrafficInfo/", request);

      return this.mapper.mapToSimUsage(response);
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to get SIM usage for account ${account}`, {
        account,
        error: message,
      });
      throw new BadRequestException(`Failed to get SIM usage: ${message}`);
    }
  }

  /**
   * Top up SIM data quota
   */
  async topUpSim(
    account: string,
    quotaMb: number,
    options: { campaignCode?: string; expiryDate?: string; scheduledAt?: string } = {}
  ): Promise<void> {
    try {
      const quotaKb = Math.round(quotaMb * 1024);
      const baseRequest: Omit<FreebitTopUpRequest, "authKey"> = {
        account,
        quota: quotaKb,
        quotaCode: options.campaignCode,
        expire: options.expiryDate,
      };

      const scheduled = !!options.scheduledAt;
      const endpoint = scheduled ? "/mvno/eachQuota/" : "/master/addSpec/";
      const request = scheduled ? { ...baseRequest, runTime: options.scheduledAt } : baseRequest;

      await this.client.makeAuthenticatedRequest<FreebitTopUpResponse, typeof request>(
        endpoint,
        request
      );

      this.logger.log(`Successfully topped up ${quotaMb}MB for account ${account}`, {
        account,
        endpoint,
        quotaMb,
        quotaKb,
        scheduled,
      });
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to top up SIM for account ${account}`, {
        account,
        quotaMb,
        error: message,
      });
      throw new BadRequestException(`Failed to top up SIM: ${message}`);
    }
  }

  /**
   * Get SIM top-up history
   */
  async getSimTopUpHistory(
    account: string,
    fromDate: string,
    toDate: string
  ): Promise<SimTopUpHistory> {
    try {
      const request: Omit<FreebitQuotaHistoryRequest, "authKey"> = {
        account,
        fromDate,
        toDate,
      };

      const response = await this.client.makeAuthenticatedRequest<
        FreebitQuotaHistoryResponse,
        typeof request
      >("/mvno/getQuotaHistory/", request);

      return this.mapper.mapToSimTopUpHistory(response, account);
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to get SIM top-up history for account ${account}`, {
        account,
        fromDate,
        toDate,
        error: message,
      });
      throw new BadRequestException(`Failed to get SIM top-up history: ${message}`);
    }
  }

  /**
   * Change SIM plan
   * Uses PA05-21 changePlan endpoint
   * 
   * IMPORTANT CONSTRAINTS:
   * - Requires runTime parameter set to 1st of following month (YYYYMMDDHHmm format)
   * - Does NOT take effect immediately (unlike PA05-06 and PA05-38)
   * - Must be done AFTER PA05-06 and PA05-38 (with 30-minute gaps)
   * - Cannot coexist with PA02-04 (cancellation) - plan changes will cancel the cancellation
   * - Must run 30 minutes apart from PA05-06 and PA05-38
   */
  async changeSimPlan(
    account: string,
    newPlanCode: string,
    options: { assignGlobalIp?: boolean; scheduledAt?: string } = {}
  ): Promise<{ ipv4?: string; ipv6?: string }> {
    try {
      this.assertOperationSpacing(account, "plan");
      // First, get current SIM details to log for debugging
      let currentPlanCode: string | undefined;
      try {
        const simDetails = await this.getSimDetails(account);
        currentPlanCode = simDetails.planCode;
        this.logger.log(`Current SIM plan details before change`, {
          account,
          currentPlanCode: simDetails.planCode,
          status: simDetails.status,
          simType: simDetails.simType,
        });
      } catch (detailsError) {
        this.logger.warn(`Could not fetch current SIM details`, {
          account,
          error: getErrorMessage(detailsError),
        });
      }

      // PA05-21 requires runTime parameter in YYYYMMDD format (8 digits, date only)
      // If not provided, default to 1st of next month
      let runTime = options.scheduledAt || undefined;
      if (!runTime) {
        const nextMonth = new Date();
        nextMonth.setMonth(nextMonth.getMonth() + 1);
        nextMonth.setDate(1);
        const year = nextMonth.getFullYear();
        const month = String(nextMonth.getMonth() + 1).padStart(2, "0");
        const day = "01";
        runTime = `${year}${month}${day}`;
        this.logger.log(`No scheduledAt provided, defaulting to 1st of next month: ${runTime}`, {
          account,
          runTime,
        });
      }

      const request: Omit<FreebitPlanChangeRequest, "authKey"> = {
        account,
        planCode: newPlanCode, // Use camelCase as required by Freebit API
        runTime: runTime, // Always include runTime for PA05-21
        // Only include globalip flag when explicitly requested
        ...(options.assignGlobalIp === true ? { globalip: "1" } : {}),
      };

      this.logger.log(`Attempting to change SIM plan via PA05-21`, {
        account,
        currentPlanCode,
        newPlanCode,
        planCode: newPlanCode,
        globalip: request.globalip,
        runTime: request.runTime,
        scheduledAt: options.scheduledAt,
      });

      const response = await this.client.makeAuthenticatedRequest<
        FreebitPlanChangeResponse,
        typeof request
      >("/mvno/changePlan/", request);

      this.logger.log(`Successfully changed plan for account ${account} to ${newPlanCode}`, {
        account,
        newPlanCode,
        assignGlobalIp: options.assignGlobalIp,
        scheduled: !!options.scheduledAt,
        response: {
          resultCode: response.resultCode,
          statusCode: response.status?.statusCode,
          message: response.status?.message,
        },
      });
      this.stampOperation(account, "plan");

      return {
        ipv4: response.ipv4,
        ipv6: response.ipv6,
      };
    } catch (error) {
      const message = getErrorMessage(error);
      
      // Extract Freebit error details if available
      const errorDetails: Record<string, unknown> = {
        account,
        newPlanCode,
        planCode: newPlanCode, // Use camelCase
        globalip: options.assignGlobalIp ? "1" : undefined,
        runTime: options.scheduledAt,
        error: message,
      };

      if (error instanceof Error) {
        errorDetails.errorName = error.name;
        errorDetails.errorMessage = error.message;
        
        // Check if it's a FreebitError with additional properties
        if ('resultCode' in error) {
          errorDetails.resultCode = error.resultCode;
        }
        if ('statusCode' in error) {
          errorDetails.statusCode = error.statusCode;
        }
        if ('statusMessage' in error) {
          errorDetails.statusMessage = error.statusMessage;
        }
      }

      this.logger.error(`Failed to change SIM plan for account ${account}`, errorDetails);
      throw new BadRequestException(`Failed to change SIM plan: ${message}`);
    }
  }

  /**
   * Update SIM features (voice options and network type)
   * 
   * IMPORTANT TIMING CONSTRAINTS from Freebit API:
   * - PA05-06 (voice features): Runs with immediate effect
   * - PA05-38 (contract line): Runs with immediate effect  
   * - PA05-21 (plan change): Requires runTime parameter, scheduled for 1st of following month
   * - These must run 30 minutes apart to avoid canceling each other
   * - PA05-06 and PA05-38 should be done first, then PA05-21 last (since it's scheduled)
   * - PA05-21 and PA02-04 (cancellation) cannot coexist
   */
  async updateSimFeatures(
    account: string,
    features: {
      voiceMailEnabled?: boolean;
      callWaitingEnabled?: boolean;
      internationalRoamingEnabled?: boolean;
      networkType?: "4G" | "5G";
    }
  ): Promise<void> {
    try {
      const voiceFeatures = {
        voiceMailEnabled: features.voiceMailEnabled,
        callWaitingEnabled: features.callWaitingEnabled,
        internationalRoamingEnabled: features.internationalRoamingEnabled,
      };

      const hasVoiceFeatures = Object.values(voiceFeatures).some(value => typeof value === "boolean");
      const hasNetworkTypeChange = typeof features.networkType === "string";

      // Execute in sequence with 30-minute delays as per Freebit API requirements
      if (hasVoiceFeatures && hasNetworkTypeChange) {
        // Both voice features and network type change requested
        this.logger.log(`Updating both voice features and network type with required 30-minute delay`, {
          account,
          hasVoiceFeatures,
          hasNetworkTypeChange,
        });

        // Step 1: Update voice features immediately (PA05-06)
        await this.updateVoiceFeatures(account, voiceFeatures);
        this.logger.log(`Voice features updated, scheduling network type change in 30 minutes`, {
          account,
          networkType: features.networkType,
        });

        // Step 2: Schedule network type change 30 minutes later (PA05-38)
        // Note: This uses setTimeout which is not ideal for production
        // Consider using a job queue like Bull or agenda for production
        setTimeout(async () => {
          try {
            await this.updateNetworkType(account, features.networkType!);
            this.logger.log(`Network type change completed after 30-minute delay`, {
              account,
              networkType: features.networkType,
            });
          } catch (error) {
            this.logger.error(`Failed to update network type after 30-minute delay`, {
              account,
              networkType: features.networkType,
              error: getErrorMessage(error),
            });
          }
        }, 30 * 60 * 1000); // 30 minutes

        this.logger.log(`Voice features updated immediately, network type scheduled for 30 minutes`, {
          account,
          voiceMailEnabled: features.voiceMailEnabled,
          callWaitingEnabled: features.callWaitingEnabled,
          internationalRoamingEnabled: features.internationalRoamingEnabled,
          networkType: features.networkType,
        });

      } else if (hasVoiceFeatures) {
        // Only voice features (PA05-06)
        await this.updateVoiceFeatures(account, voiceFeatures);
        this.logger.log(`Voice features updated successfully`, {
          account,
          voiceMailEnabled: features.voiceMailEnabled,
          callWaitingEnabled: features.callWaitingEnabled,
          internationalRoamingEnabled: features.internationalRoamingEnabled,
        });

      } else if (hasNetworkTypeChange) {
        // Only network type change (PA05-38)
        await this.updateNetworkType(account, features.networkType!);
        this.logger.log(`Network type updated successfully`, {
          account,
          networkType: features.networkType,
        });
      }

      this.logger.log(`Successfully updated SIM features for account ${account}`, {
        account,
        voiceMailEnabled: features.voiceMailEnabled,
        callWaitingEnabled: features.callWaitingEnabled,
        internationalRoamingEnabled: features.internationalRoamingEnabled,
        networkType: features.networkType,
      });
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to update SIM features for account ${account}`, {
        account,
        features,
        error: message,
        errorStack: error instanceof Error ? error.stack : undefined,
      });
      throw new BadRequestException(`Failed to update SIM features: ${message}`);
    }
  }

  /**
   * Update voice features (voicemail, call waiting, international roaming)
   * Uses PA05-06 MVNO Voice Option Change endpoint - runs with immediate effect
   * 
   * Error codes specific to PA05-06:
   * - 243: Voice option (list) problem
   * - 244: Voicemail parameter problem
   * - 245: Call waiting parameter problem  
   * - 250: WORLD WING parameter problem
   */
  private async updateVoiceFeatures(
    account: string,
    features: {
      voiceMailEnabled?: boolean;
      callWaitingEnabled?: boolean;
      internationalRoamingEnabled?: boolean;
    }
  ): Promise<void> {
    try {
      this.assertOperationSpacing(account, "voice");

      const buildVoiceOptionPayload = (): Omit<FreebitVoiceOptionRequest, "authKey"> => {
        const talkOption: FreebitVoiceOptionSettings = {};

        if (typeof features.voiceMailEnabled === "boolean") {
          talkOption.voiceMail = features.voiceMailEnabled ? "10" : "20";
        }

        if (typeof features.callWaitingEnabled === "boolean") {
          talkOption.callWaiting = features.callWaitingEnabled ? "10" : "20";
        }

        if (typeof features.internationalRoamingEnabled === "boolean") {
          talkOption.worldWing = features.internationalRoamingEnabled ? "10" : "20";
          if (features.internationalRoamingEnabled) {
            talkOption.worldWingCreditLimit = "50000"; // minimum permitted when enabling
          }
        }

        if (Object.keys(talkOption).length === 0) {
          throw new BadRequestException("No voice options specified for update");
        }

        return {
          account,
          userConfirmed: "10",
          aladinOperated: "10",
          talkOption,
        };
      };

      const voiceOptionPayload = buildVoiceOptionPayload();

      this.logger.debug("Submitting voice option change via /mvno/talkoption/changeOrder/ (PA05-06)", {
        account,
        payload: voiceOptionPayload,
      });

      await this.client.makeAuthenticatedRequest<
        FreebitVoiceOptionResponse,
        typeof voiceOptionPayload
      >("/mvno/talkoption/changeOrder/", voiceOptionPayload);

      this.logger.log("Voice option change completed via PA05-06", {
        account,
        voiceMailEnabled: features.voiceMailEnabled,
        callWaitingEnabled: features.callWaitingEnabled,
        internationalRoamingEnabled: features.internationalRoamingEnabled,
      });
      this.stampOperation(account, "voice");

      // Save to database for future retrieval
      if (this.voiceOptionsService) {
        try {
          await this.voiceOptionsService.saveVoiceOptions(account, features);
        } catch (dbError) {
          this.logger.warn("Failed to save voice options to database (non-fatal)", {
            account,
            error: getErrorMessage(dbError),
          });
        }
      }

      return;

    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to update voice features for account ${account}`, {
        account,
        features,
        error: message,
        errorStack: error instanceof Error ? error.stack : undefined,
      });
      throw new BadRequestException(`Failed to update voice features: ${message}`);
    }
  }

  /**
   * Update network type (4G/5G)
   * Uses PA05-38 contract line change - runs with immediate effect
   * NOTE: Must be called 30 minutes after PA05-06 if both are being updated
   */
  private async updateNetworkType(account: string, networkType: "4G" | "5G"): Promise<void> {
    try {
      this.assertOperationSpacing(account, "network");
      let eid: string | undefined;
      let productNumber: string | undefined;
      try {
        const details = await this.getSimDetails(account);
        if (details.eid) {
          eid = details.eid;
        } else if (details.iccid) {
          productNumber = details.iccid;
        }
        this.logger.debug(`Resolved SIM identifiers for contract line change`, {
          account,
          eid,
          productNumber,
          currentNetworkType: details.networkType,
        });
        if (details.networkType?.toUpperCase() === networkType.toUpperCase()) {
          this.logger.log(`Network type already ${networkType} for account ${account}; skipping update.`, {
            account,
            networkType,
          });
          return;
        }
      } catch (resolveError) {
        this.logger.warn(`Unable to resolve SIM identifiers before contract line change`, {
          account,
          error: getErrorMessage(resolveError),
        });
      }

      const request: Omit<FreebitContractLineChangeRequest, "authKey"> = {
        account,
        contractLine: networkType,
        ...(eid ? { eid } : {}),
        ...(productNumber ? { productNumber } : {}),
      };

      this.logger.debug(`Updating network type via PA05-38 for account ${account}`, {
        account,
        networkType,
        request,
      });

      const response = await this.client.makeAuthenticatedJsonRequest<
        FreebitContractLineChangeResponse,
        typeof request
      >("/mvno/contractline/change/", request);

      this.logger.log(`Successfully updated network type for account ${account}`, {
        account,
        networkType,
        resultCode: response.resultCode,
        statusCode: response.status?.statusCode,
        message: response.status?.message,
      });
      this.stampOperation(account, "network");

      // Save to database for future retrieval
      if (this.voiceOptionsService) {
        try {
          await this.voiceOptionsService.saveVoiceOptions(account, { networkType });
        } catch (dbError) {
          this.logger.warn("Failed to save network type to database (non-fatal)", {
            account,
            error: getErrorMessage(dbError),
          });
        }
      }
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to update network type for account ${account}`, {
        account,
        networkType,
        error: message,
        errorStack: error instanceof Error ? error.stack : undefined,
      });
      throw new BadRequestException(`Failed to update network type: ${message}`);
    }
  }

  /**
   * Cancel SIM service
   * Uses PA02-04 cancellation endpoint
   * 
   * IMPORTANT CONSTRAINTS:
   * - Must be sent with runDate as 1st of client's cancellation month n+1
   *   (e.g., cancel end of Jan = runDate 20250201)
   * - After PA02-04 is sent, any subsequent PA05-21 calls will cancel it
   * - PA05-21 and PA02-04 cannot coexist
   * - Must prevent clients from making further changes after cancellation is requested
   */
  async cancelSim(account: string, scheduledAt?: string): Promise<void> {
    try {
      const request: Omit<FreebitCancelPlanRequest, "authKey"> = {
        account,
        runTime: scheduledAt,
      };

      this.logger.log(`Cancelling SIM service via PA02-04 for account ${account}`, {
        account,
        runTime: scheduledAt,
        note: "After this, PA05-21 plan changes will cancel the cancellation",
      });

      await this.client.makeAuthenticatedRequest<FreebitCancelPlanResponse, typeof request>(
        "/mvno/releasePlan/",
        request
      );

      this.logger.log(`Successfully cancelled SIM for account ${account}`, {
        account,
        runTime: scheduledAt,
      });
      this.stampOperation(account, "cancellation");
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to cancel SIM for account ${account}`, {
        account,
        scheduledAt,
        error: message,
      });
      throw new BadRequestException(`Failed to cancel SIM: ${message}`);
    }
  }

  /**
   * Reissue eSIM profile (simple version)
   */
  async reissueEsimProfile(account: string): Promise<void> {
    try {
      const request: Omit<FreebitEsimReissueRequest, "authKey"> = {
        requestDatas: [{ kind: "MVNO", account }],
      };

      await this.client.makeAuthenticatedRequest<FreebitEsimReissueResponse, typeof request>(
        "/mvno/reissueEsim/",
        request
      );

      this.logger.log(`Successfully reissued eSIM profile for account ${account}`);
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to reissue eSIM profile for account ${account}`, {
        account,
        error: message,
      });
      throw new BadRequestException(`Failed to reissue eSIM profile: ${message}`);
    }
  }

  /**
   * Reissue eSIM profile with enhanced options
   */
  async reissueEsimProfileEnhanced(
    account: string,
    newEid: string,
    options: { oldProductNumber?: string; oldEid?: string; planCode?: string } = {}
  ): Promise<void> {
    try {
      const request: Omit<FreebitEsimAddAccountRequest, "authKey"> = {
        aladinOperated: "20",
        account,
        eid: newEid,
        addKind: "R",
        planCode: options.planCode,
      };

      await this.client.makeAuthenticatedRequest<FreebitEsimAddAccountResponse, typeof request>(
        "/mvno/esim/addAcnt/",
        request
      );

      this.logger.log(`Successfully reissued eSIM profile via addAcnt for account ${account}`, {
        account,
        newEid,
        oldProductNumber: options.oldProductNumber,
        oldEid: options.oldEid,
      });
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to reissue eSIM profile via addAcnt for account ${account}`, {
        account,
        newEid,
        error: message,
      });
      throw new BadRequestException(`Failed to reissue eSIM profile: ${message}`);
    }
  }

  /**
   * Activate new eSIM account using PA05-41 (addAcct)
   */
  async activateEsimAccountNew(params: {
    account: string;
    eid: string;
    planCode?: string;
    contractLine?: "4G" | "5G";
    aladinOperated?: "10" | "20";
    shipDate?: string;
    addKind?: "N" | "M" | "R";  // N:新規, M:MNP転入, R:再発行
    simKind?: "E0" | "E2" | "E3";  // E0:音声あり, E2:SMSなし, E3:SMSあり (Required except when addKind='R')
    repAccount?: string;  // 代表番号
    deliveryCode?: string;  // 顧客コード
    globalIp?: "10" | "20";  // 10:なし, 20:あり
    mnp?: { reserveNumber: string; reserveExpireDate?: string };
    identity?: {
      firstnameKanji?: string;
      lastnameKanji?: string;
      firstnameZenKana?: string;
      lastnameZenKana?: string;
      gender?: string;
      birthday?: string;
    };
  }): Promise<void> {
    const {
      account,
      eid,
      planCode,
      contractLine,
      aladinOperated = "10",
      shipDate,
      addKind,
      simKind,
      repAccount,
      deliveryCode,
      globalIp,
      mnp,
      identity,
    } = params;

    if (!account || !eid) {
      throw new BadRequestException("activateEsimAccountNew requires account and eid");
    }

    const finalAddKind = addKind || "N";

    // Validate simKind: Required except when addKind is 'R' (reissue)
    if (finalAddKind !== "R" && !simKind) {
      throw new BadRequestException(
        "simKind is required for eSIM activation (use 'E0' for voice, 'E3' for SMS, 'E2' for data-only)"
      );
    }

    try {
      const payload: FreebitEsimAccountActivationRequest = {
        authKey: await this.auth.getAuthKey(),
        aladinOperated,
        createType: "new",
        eid,
        account,
        simkind: simKind || "E0",  // Default to voice-enabled if not specified
        addKind: finalAddKind,
        planCode,
        contractLine,
        shipDate,
        repAccount,
        deliveryCode,
        globalIp,
        ...(mnp ? { mnp } : {}),
        ...(identity ? identity : {}),
      } as FreebitEsimAccountActivationRequest;

      // Use JSON request for PA05-41
      await this.client.makeAuthenticatedJsonRequest<
        FreebitEsimAccountActivationResponse,
        FreebitEsimAccountActivationRequest
      >("/mvno/esim/addAcct/", payload);

      this.logger.log("Successfully activated new eSIM account via PA05-41", {
        account,
        planCode,
        contractLine,
        addKind: addKind || "N",
        scheduled: !!shipDate,
        mnp: !!mnp,
      });
    } catch (error) {
      const message = getErrorMessage(error);
      this.logger.error(`Failed to activate new eSIM account ${account}`, {
        account,
        eid,
        planCode,
        addKind,
        error: message,
      });
      throw new BadRequestException(`Failed to activate new eSIM account: ${message}`);
    }
  }

  /**
   * Health check - test API connectivity
   */
  async healthCheck(): Promise<boolean> {
    try {
      // Try a simple endpoint first
      const simpleCheck = await this.client.makeSimpleRequest("/");
      if (simpleCheck) {
        return true;
      }

      // If simple check fails, try authenticated request
      await this.auth.getAuthKey();
      return true;
    } catch (error) {
      this.logger.debug("Freebit health check failed", {
        error: getErrorMessage(error),
      });
      return false;
    }
  }
}
