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 crypto from "node:crypto";

interface CardInitiateRequest {
  amount: number;
  currency: string;
  description: string;
  metadata: {
    merchantOrderId: string;
    orderRef: string;
  };
  return_url: string;
  webhook_url: string;
}

interface CardInitiateResponse {
  id: string;
  status: string;
  client_secret: string;
  checkout_url: string;
  expires_at: number;
}

interface CardQueryResponse {
  id: string;
  status: string;
  amount: number;
  currency: string;
  transaction_id?: string;
  paid_at?: number;
  failure_code?: string;
  failure_message?: string;
}

@Injectable()
export class CardProvider implements PaymentProvider {
  readonly method = ProviderMethod.CARD;
  private readonly logger = new Logger(CardProvider.name);

  constructor(
    private readonly config: ConfigService,
    private readonly http: HttpService,
  ) {}

  async initiate(
    input: ProviderInitiationInput,
  ): Promise<ProviderInitiationResult> {
    const amount = input.amountMinor / 100;

    const requestBody: CardInitiateRequest = {
      amount,
      currency: input.currency,
      description: `EDR ${input.orderRef}`,
      metadata: {
        merchantOrderId: input.merchantOrderId,
        orderRef: input.orderRef,
      },
      // Per-transaction browser return target (each calling app has its own UI); config is fallback.
      return_url: input.returnUrl ?? this.returnUrl,
      webhook_url: this.webhookUrl,
    };

    const response = await this.postJson<CardInitiateResponse>(
      `${this.baseUrl}/v1/payment_intents`,
      requestBody,
    );

    if (!response.id) {
      throw new Error(
        `Card gateway initiate failed: ${JSON.stringify(response)}`,
      );
    }

    const expiresAt = new Date(response.expires_at * 1000);

    return {
      providerOrderId: response.id,
      clientAction: { type: "REDIRECT", url: response.checkout_url },
      expiresAt,
      rawInitiation: {
        request: requestBody,
        response,
      },
    };
  }

  async queryStatus(merchantOrderId: string): Promise<ProviderStatus> {
    // For card payments, we need to find the payment intent by metadata
    // In a real implementation, we'd store the provider order ID and use it directly
    const response = await this.getJson<CardQueryResponse>(
      `${this.baseUrl}/v1/payment_intents/search?metadata[merchantOrderId]=${merchantOrderId}`,
    );

    const mapped = this.mapStatus(response.status);

    return {
      status: mapped,
      providerTxnId: response.transaction_id,
      failureCode: response.failure_code,
      failureMessage: response.failure_message,
      rawResponse: response as unknown as Record<string, unknown>,
    };
  }

  verifyWebhookSignature(
    payload: Record<string, unknown>,
    signature: string,
  ): boolean {
    const payloadString = JSON.stringify(payload);
    const expectedSignature = crypto
      .createHmac("sha256", this.webhookSecret)
      .update(payloadString)
      .digest("hex");

    try {
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature),
      );
    } catch {
      return false;
    }
  }

  mapWebhookStatus(status: string): ProviderPaymentStatus {
    return this.mapStatus(status);
  }

  private mapStatus(status: string): ProviderPaymentStatus {
    switch (status?.toLowerCase()) {
      case "succeeded":
      case "paid":
        return ProviderPaymentStatus.SUCCEEDED;
      case "failed":
      case "canceled":
      case "expired":
        return ProviderPaymentStatus.FAILED;
      case "requires_payment_method":
      case "requires_confirmation":
      case "requires_action":
        return ProviderPaymentStatus.REQUIRES_ACTION;
      case "processing":
        return ProviderPaymentStatus.PROCESSING;
      default:
        return ProviderPaymentStatus.PROCESSING;
    }
  }

  private async postJson<T>(url: string, body: unknown): Promise<T> {
    const config: AxiosRequestConfig = {
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${this.apiKey}`,
      },
      timeout: 10_000,
    };

    const started = Date.now();
    try {
      const res = await firstValueFrom(this.http.post<T>(url, body, config));
      this.logger.debug(
        `Card Gateway POST ${url} status=${res.status} latency=${Date.now() - started}ms`,
      );
      return res.data;
    } catch (err) {
      if (err instanceof AxiosError) {
        this.logger.error(
          `Card Gateway POST ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)}`,
        );
      } else {
        this.logger.error(
          `Card Gateway POST ${url} threw: ${err instanceof Error ? err.message : err}`,
        );
      }
      throw err;
    }
  }

  private async getJson<T>(url: string): Promise<T> {
    const config: AxiosRequestConfig = {
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
      },
      timeout: 10_000,
    };

    const started = Date.now();
    try {
      const res = await firstValueFrom(this.http.get<T>(url, config));
      this.logger.debug(
        `Card Gateway GET ${url} status=${res.status} latency=${Date.now() - started}ms`,
      );
      return res.data;
    } catch (err) {
      if (err instanceof AxiosError) {
        this.logger.error(
          `Card Gateway GET ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)}`,
        );
      } else {
        this.logger.error(
          `Card Gateway GET ${url} threw: ${err instanceof Error ? err.message : err}`,
        );
      }
      throw err;
    }
  }

  private get baseUrl(): string {
    return this.config.get<string>("card.baseUrl") ?? "";
  }
  private get apiKey(): string {
    return this.config.get<string>("card.apiKey") ?? "";
  }
  private get webhookSecret(): string {
    return this.config.get<string>("card.webhookSecret") ?? "";
  }
  private get webhookUrl(): string {
    return this.config.get<string>("card.webhookUrl") ?? "";
  }
  private get returnUrl(): string {
    return this.config.get<string>("card.returnUrl") ?? "";
  }
}
