import { Injectable, Inject, BadRequestException, Optional } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { FreebitClientService } from "./freebit-client.service.js";
import { FreebitRateLimiterService } from "./freebit-rate-limiter.service.js";
import { FreebitAccountService } from "./freebit-account.service.js";
import type { VoiceOptionsService } from "@bff/modules/voice-options/services/voice-options.service.js";
import type {
  FreebitVoiceOptionSettings,
  FreebitVoiceOptionRequest,
  FreebitVoiceOptionResponse,
  FreebitContractLineChangeRequest,
  FreebitContractLineChangeResponse,
} from "../interfaces/freebit.types.js";

export interface VoiceFeatures {
  voiceMailEnabled?: boolean | undefined;
  callWaitingEnabled?: boolean | undefined;
  internationalRoamingEnabled?: boolean | undefined;
}

/**
 * Service for Freebit voice features and network type operations.
 * Handles PA05-06 (voice options) and PA05-38 (network type) operations.
 */
@Injectable()
export class FreebitVoiceService {
  constructor(
    private readonly client: FreebitClientService,
    private readonly rateLimiter: FreebitRateLimiterService,
    private readonly accountService: FreebitAccountService,
    @Inject(Logger) private readonly logger: Logger,
    @Optional()
    @Inject("SimVoiceOptionsService")
    private readonly voiceOptionsService?: VoiceOptionsService
  ) {}

  /**
   * 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
   * - These must run 30 minutes apart to avoid canceling each other
   */
  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";

      // Freebit API requires voice features and network type changes to be 30 minutes apart.
      if (hasVoiceFeatures && hasNetworkTypeChange) {
        throw new BadRequestException(
          "Cannot update voice features and network type simultaneously. " +
            "Voice and network type changes must be 30 minutes apart per Freebit API requirements. " +
            "Use SimManagementQueueService to schedule the network type change after voice updates."
        );
      }

      // Validate that at least one feature is specified
      if (!hasVoiceFeatures && !hasNetworkTypeChange) {
        throw new BadRequestException(
          "No features specified for update. Please provide at least one of: " +
            "voiceMailEnabled, callWaitingEnabled, internationalRoamingEnabled, or networkType."
        );
      }

      if (hasVoiceFeatures) {
        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) {
        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 = extractErrorMessage(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
   */
  async updateVoiceFeatures(account: string, features: VoiceFeatures): Promise<void> {
    try {
      await this.rateLimiter.executeWithSpacing(account, "voice", async () => {
        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";
            }
          }

          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,
        });

        // 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: extractErrorMessage(dbError),
            });
          }
        }
      });
    } catch (error) {
      const message = extractErrorMessage(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
   */
  async updateNetworkType(account: string, networkType: "4G" | "5G"): Promise<void> {
    let eid: string | undefined;
    let productNumber: string | undefined;

    try {
      try {
        const details = await this.accountService.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: extractErrorMessage(resolveError),
        });
      }

      await this.rateLimiter.executeWithSpacing(account, "network", async () => {
        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,
        });

        // 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: extractErrorMessage(dbError),
            });
          }
        }
      });
    } catch (error) {
      const message = extractErrorMessage(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}`);
    }
  }
}
