import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { HttpService } from "@nestjs/axios";
import {
  PaymentProvider,
  ProviderInitiationInput,
  ProviderInitiationResult,
  ProviderStatus,
  ProviderPaymentStatus,
  ProviderMethod,
} from "@edr/types";
import { AxiosError, AxiosRequestConfig } from "axios";
import { firstValueFrom } from "rxjs";
import * as https from "node:https";
import {
  createNonceStr,
  createTimestamp,
  signRequestObject,
  verifyRequestObject,
} from "../telebirr/telebirr.crypto";
import {
  DMoneyFabricTokenResponse,
  DMoneyPreOrderRequest,
  DMoneyPreOrderResponse,
  DMoneyQueryOrderResponse,
} from "./dmoney.types";

const DMONEY_HTTP_TIMEOUT_MS = 10_000;

/**
 * D-Money (Djibouti) shares the same payment-gateway platform as Telebirr: fabric-token auth,
 * payment.preorder / payment.queryorder, SHA256withRSA (PSS) signing, and a signed paygate
 * web-checkout redirect. This provider mirrors TelebirrProvider, differing only in endpoint
 * paths, the already-"Bearer"-prefixed token, the queryOrder status field (order_status), and
 * the web-only client action (no LAUNCH_APP). Crypto is reused from telebirr.crypto (RSA-PSS).
 */
@Injectable()
export class DMoneyProvider implements PaymentProvider {
  readonly method = ProviderMethod.DMONEY;
  private readonly logger = new Logger(DMoneyProvider.name);
  private readonly httpsAgent: https.Agent;

  constructor(
    private readonly config: ConfigService,
    private readonly http: HttpService,
  ) {
    const insecure = this.config.get<boolean>("dmoney.insecureTls");
    if (insecure) {
      this.logger.warn(
        "DMONEY_INSECURE_TLS=true — TLS verification disabled for D-Money calls. DEV ONLY.",
      );
    }
    this.httpsAgent = new https.Agent({
      rejectUnauthorized: !insecure,
      secureProtocol: "TLSv1_2_method",
    });
  }

  async initiate(
    input: ProviderInitiationInput,
  ): Promise<ProviderInitiationResult> {
    const fabricToken = await this.applyFabricToken();
    const requestBody = this.buildPreOrderRequest(input);
    const response = await this.postJson<DMoneyPreOrderResponse>(
      `${this.baseUrl}/apiaccess/payment/gateway/payment/v1/merchant/preOrder`,
      requestBody,
      {
        "Content-Type": "application/json",
        "X-APP-Key": this.fabricAppId,
        Authorization: fabricToken,
      },
    );

    const prepayId = response.biz_content?.prepay_id;
    if (response.result !== "SUCCESS" || !prepayId) {
      throw new Error(
        `D-Money preOrder failed: ${JSON.stringify(response)}`,
      );
    }

    const expiresAt = this.computeExpiresAt(
      requestBody.biz_content.timeout_express,
    );

    return {
      providerOrderId: prepayId,
      clientAction: {
        type: "REDIRECT",
        url: this.buildCheckoutUrl(prepayId),
      },
      expiresAt,
      rawInitiation: {
        request: this.sanitize(requestBody),
        response,
      },
    };
  }

  async queryStatus(merchantOrderId: string): Promise<ProviderStatus> {
    const fabricToken = await this.applyFabricToken();
    const requestBody = this.buildQueryOrderRequest(merchantOrderId);
    const response = await this.postJson<DMoneyQueryOrderResponse>(
      `${this.baseUrl}/apiaccess/payment/v1/merchant/queryOrder`,
      requestBody,
      {
        "Content-Type": "application/json",
        "X-APP-Key": this.fabricAppId,
        Authorization: fabricToken,
      },
    );

    const orderStatus = response.biz_content?.order_status;
    const providerTxnId = response.biz_content?.payment_order_id;
    const mapped = this.mapOrderStatus(orderStatus);

    return {
      status: mapped,
      providerTxnId,
      failureCode:
        mapped === ProviderPaymentStatus.FAILED && orderStatus
          ? orderStatus
          : undefined,
      rawResponse: response as Record<string, unknown>,
    };
  }

  /** queryOrder `order_status` → shared status. */
  mapOrderStatus(orderStatus: string | undefined): ProviderPaymentStatus {
    switch (orderStatus) {
      case "PAY_SUCCESS":
      case "Completed":
      case "SUCCESS":
        return ProviderPaymentStatus.SUCCEEDED;
      case "PAY_FAILED":
      case "Failure":
      case "ORDER_CLOSED":
      case "Expired":
        return ProviderPaymentStatus.FAILED;
      case "WAIT_PAY":
        return ProviderPaymentStatus.REQUIRES_ACTION;
      case "PAYING":
      case "Paying":
        return ProviderPaymentStatus.PROCESSING;
      default:
        return ProviderPaymentStatus.PROCESSING;
    }
  }

  /** Notification `trade_status` → shared status. */
  mapWebhookTradeStatus(
    tradeStatus: string | undefined,
  ): ProviderPaymentStatus {
    switch (tradeStatus) {
      case "Completed":
        return ProviderPaymentStatus.SUCCEEDED;
      case "Failure":
      case "Expired":
        return ProviderPaymentStatus.FAILED;
      case "Paying":
        return ProviderPaymentStatus.PROCESSING;
      default:
        return ProviderPaymentStatus.PROCESSING;
    }
  }

  verifyWebhookSignature(payload: Record<string, unknown>): boolean {
    if (!this.publicKey) {
      this.logger.error(
        "DMONEY_PUBLIC_KEY not configured; rejecting all webhooks",
      );
      return false;
    }
    return verifyRequestObject(payload, this.publicKey);
  }

  private async applyFabricToken(): Promise<string> {
    const response = await this.postJson<DMoneyFabricTokenResponse>(
      `${this.baseUrl}/apiaccess/payment/gateway/payment/v1/token`,
      { appSecret: this.appSecret },
      {
        "Content-Type": "application/json",
        "X-APP-Key": this.fabricAppId,
      },
    );
    if (!response?.token) {
      throw new Error(
        `D-Money token request failed: ${JSON.stringify(response)}`,
      );
    }
    // D-Money returns the token already prefixed with "Bearer " — use it verbatim.
    return response.token;
  }

  private buildPreOrderRequest(
    input: ProviderInitiationInput,
  ): DMoneyPreOrderRequest {
    const totalAmount = (input.amountMinor).toFixed(2);
    const redirectUrl = input.redirectUrl ?? this.returnUrl;
    const req = {
      timestamp: createTimestamp(),
      nonce_str: createNonceStr(),
      method: "payment.preorder" as const,
      version: "1.0" as const,
      biz_content: {
        notify_url: this.notifyUrl,
        appid: this.merchantAppId,
        merch_code: this.merchantCode,
        merch_order_id: input.merchantOrderId,
        trade_type: "Checkout" as const,
        title: `EDR ${input.orderRef}`,
        total_amount: totalAmount,
        trans_currency:  1 == 1 ? "DJF": this.currency,
        timeout_express: this.timeoutExpress,
        ...(redirectUrl ? { redirect_url: redirectUrl } : {}),
      },
    };

    console.log("\n\n\n")
    console.log(req)
    console.log("\n\n\n")
    const sign = signRequestObject(
      req as unknown as Record<string, unknown>,
      this.privateKey,
    );
    return { ...req, sign, sign_type: "SHA256WithRSA" };
  }

  private buildQueryOrderRequest(
    merchantOrderId: string,
  ): Record<string, unknown> {
    const req = {
      timestamp: createTimestamp(),
      nonce_str: createNonceStr(),
      method: "payment.queryorder",
      version: "1.0",
      biz_content: {
        appid: this.merchantAppId,
        merch_code: this.merchantCode,
        merch_order_id: merchantOrderId,
      },
    };
    const sign = signRequestObject(
      req as Record<string, unknown>,
      this.privateKey,
    );
    return { ...req, sign, sign_type: "SHA256WithRSA" };
  }

  private buildCheckoutUrl(prepayId: string): string {
    // Only these five fields are signed for the paygate URL.
    const map: Record<string, string> = {
      appid: this.merchantAppId,
      merch_code: this.merchantCode,
      nonce_str: createNonceStr(),
      prepay_id: prepayId,
      timestamp: createTimestamp(),
    };
    const sign = signRequestObject(map, this.privateKey);
    const query = [
      `appid=${map.appid}`,
      `merch_code=${map.merch_code}`,
      `nonce_str=${map.nonce_str}`,
      `prepay_id=${map.prepay_id}`,
      `timestamp=${map.timestamp}`,
      `sign=${sign}`,
      "sign_type=SHA256WithRSA",
      "version=1.0",
      "trade_type=Checkout",
      `language=${this.language}`,
    ].join("&");
    return `${this.webBaseUrl}/payment/web/paygate?${query}`;
  }

  private computeExpiresAt(timeoutExpress: string): Date {
    const match = /^(\d+)m$/.exec(timeoutExpress);
    const minutes = match ? parseInt(match[1], 10) : 120;
    return new Date(Date.now() + minutes * 60_000);
  }

  private async postJson<T>(
    url: string,
    body: unknown,
    headers: Record<string, string>,
  ): Promise<T> {
    const config: AxiosRequestConfig = {
      headers,
      timeout: DMONEY_HTTP_TIMEOUT_MS,
      httpsAgent: this.httpsAgent,
    };
    const started = Date.now();
    try {
      const res = await firstValueFrom(this.http.post<T>(url, body, config));
      this.logger.debug(
        `D-Money POST ${url} status=${res.status} latency=${Date.now() - started}ms`,
      );
      return res.data;
    } catch (err) {
      if (err instanceof AxiosError) {
        this.logger.error(
          `D-Money POST ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)} code=${err.code} message=${err.message}`,
        );
      } else {
        this.logger.error(
          `D-Money POST ${url} threw: ${err instanceof Error ? err.message : err}`,
        );
      }
      throw err;
    }
  }

  private sanitize(body: DMoneyPreOrderRequest): Record<string, unknown> {
    const { sign: _sign, ...rest } = body;
    return rest;
  }

  private get baseUrl(): string {
    return this.config.get<string>("dmoney.baseUrl") ?? "";
  }
  private get webBaseUrl(): string {
    return this.config.get<string>("dmoney.webBaseUrl") ?? "";
  }
  private get fabricAppId(): string {
    return this.config.get<string>("dmoney.fabricAppId") ?? "";
  }
  private get appSecret(): string {
    return this.config.get<string>("dmoney.appSecret") ?? "";
  }
  private get merchantAppId(): string {
    return this.config.get<string>("dmoney.merchantAppId") ?? "";
  }
  private get merchantCode(): string {
    return this.config.get<string>("dmoney.merchantCode") ?? "";
  }
  private get notifyUrl(): string {
    return this.config.get<string>("dmoney.notifyUrl") ?? "";
  }
  private get returnUrl(): string {
    return this.config.get<string>("dmoney.returnUrl") ?? "";
  }
  private get timeoutExpress(): string {
    return this.config.get<string>("dmoney.timeoutExpress") ?? "120m";
  }
  private get language(): string {
    return this.config.get<string>("dmoney.language") ?? "en";
  }
  private get currency(): string {
    return this.config.get<string>("dmoney.currency") ?? "FDJ";
  }
  private get privateKey(): string {
    return this.config.get<string>("dmoney.privateKey") ?? "";
  }
  private get publicKey(): string {
    return this.config.get<string>("dmoney.publicKey") ?? "";
  }
}
