import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client';
import { AxiosError, AxiosRequestConfig } from 'axios';
import { firstValueFrom } from 'rxjs';
import {
  PaymentProvider,
  ProviderInitiationInput,
  ProviderInitiationResult,
  ProviderStatus,
} from '../payments.types';

const WAAFI_HTTP_TIMEOUT_MS = 10_000;

interface WaafiInitiateRequest {
  schemaVersion: string;
  requestId: string;
  timestamp: string;
  channelName: string;
  serviceName: string;
  serviceParams: {
    merchantUid: string;
    apiUserId: string;
    apiKey: string;
    paymentMethod: string;
    payerInfo: {
      accountNo: string;
    };
    transactionInfo: {
      referenceId: string;
      invoiceId: string;
      amount: number;
      currency: string;
      description: string;
    };
  };
}

interface WaafiInitiateResponse {
  responseCode: string;
  responseMsg: string;
  params?: {
    state: string;
    referenceId: string;
    transactionId: string;
    checkoutUrl?: string;
  };
}

interface WaafiQueryRequest {
  schemaVersion: string;
  requestId: string;
  timestamp: string;
  channelName: string;
  serviceName: string;
  serviceParams: {
    merchantUid: string;
    apiUserId: string;
    apiKey: string;
    transactionId?: string;
    referenceId?: string;
  };
}

interface WaafiQueryResponse {
  responseCode: string;
  responseMsg: string;
  params?: {
    state: string;
    referenceId: string;
    transactionId: string;
    amount: number;
    currency: string;
    paidAmount?: number;
  };
}

@Injectable()
export class WaafiProvider implements PaymentProvider {
  readonly method = PaymentMethodType.WAAFI;
  private readonly logger = new Logger(WaafiProvider.name);

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

  async initiate(input: ProviderInitiationInput): Promise<ProviderInitiationResult> {
    const requestBody = this.buildInitiateRequest(input);
    const response = await this.postJson<WaafiInitiateResponse>(
      `${this.baseUrl}/asm`,
      requestBody,
    );

    if (response.responseCode !== '2001') {
      throw new Error(
        `Waafi initiate failed: ${response.responseCode} - ${response.responseMsg}`,
      );
    }

    const transactionId = response.params?.transactionId;
    const checkoutUrl = response.params?.checkoutUrl || `${this.baseUrl}/checkout?ref=${transactionId}`;

    if (!transactionId) {
      throw new Error(`Waafi returned no transactionId: ${JSON.stringify(response)}`);
    }

    const expiresAt = new Date(Date.now() + 15 * 60_000); // 15 minutes

    return {
      providerOrderId: transactionId,
      clientAction: { type: 'REDIRECT', url: checkoutUrl },
      expiresAt,
      rawInitiation: {
        request: this.sanitize(requestBody),
        response,
      },
    };
  }

  async queryStatus(merchantOrderId: string): Promise<ProviderStatus> {
    const requestBody = this.buildQueryRequest(merchantOrderId);
    const response = await this.postJson<WaafiQueryResponse>(
      `${this.baseUrl}/asm`,
      requestBody,
    );

    const state = response.params?.state;
    const transactionId = response.params?.transactionId;
    const mapped = this.mapState(state);

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

  mapState(state: string | undefined): PaymentIntentStatus {
    switch (state) {
      case 'APPROVED':
      case 'SUCCESS':
        return PaymentIntentStatus.SUCCEEDED;
      case 'FAILED':
      case 'DECLINED':
      case 'CANCELLED':
      case 'EXPIRED':
        return PaymentIntentStatus.FAILED;
      case 'PENDING':
      case 'INITIATED':
        return PaymentIntentStatus.REQUIRES_ACTION;
      case 'PROCESSING':
        return PaymentIntentStatus.PROCESSING;
      default:
        return PaymentIntentStatus.PROCESSING;
    }
  }

  verifyWebhookSignature(payload: Record<string, unknown>): boolean {
    // Waafi webhook signature verification
    // Implementation depends on Waafi's webhook signature mechanism
    const signature = payload.signature as string;
    const apiKey = this.apiKey;

    if (!signature || !apiKey) {
      this.logger.error('Waafi webhook missing signature or API key not configured');
      return false;
    }

    // TODO: Implement actual signature verification based on Waafi documentation
    // For now, basic validation
    return signature.length > 0;
  }

  private buildInitiateRequest(input: ProviderInitiationInput): WaafiInitiateRequest {
    const amount = input.amountMinor / 100; // Convert minor units to major

    return {
      schemaVersion: '1.0',
      requestId: this.generateRequestId(),
      timestamp: new Date().toISOString(),
      channelName: 'WEB',
      serviceName: 'API_PURCHASE',
      serviceParams: {
        merchantUid: this.merchantUid,
        apiUserId: this.apiUserId,
        apiKey: this.apiKey,
        paymentMethod: 'MWALLET_ACCOUNT',
        payerInfo: {
          accountNo: 'CUSTOMER', // Customer enters their number on Waafi page
        },
        transactionInfo: {
          referenceId: input.merchantOrderId,
          invoiceId: input.bookingRef,
          amount,
          currency: input.currency === 'ETB' ? 'DJF' : input.currency, // Convert ETB to DJF
          description: `EDR Train Booking ${input.bookingRef}`,
        },
      },
    };
  }

  private buildQueryRequest(merchantOrderId: string): WaafiQueryRequest {
    return {
      schemaVersion: '1.0',
      requestId: this.generateRequestId(),
      timestamp: new Date().toISOString(),
      channelName: 'WEB',
      serviceName: 'API_QUERY',
      serviceParams: {
        merchantUid: this.merchantUid,
        apiUserId: this.apiUserId,
        apiKey: this.apiKey,
        referenceId: merchantOrderId,
      },
    };
  }

  private generateRequestId(): string {
    return `EDR-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
  }

  private async postJson<T>(url: string, body: unknown): Promise<T> {
    const config: AxiosRequestConfig = {
      headers: {
        'Content-Type': 'application/json',
      },
      timeout: WAAFI_HTTP_TIMEOUT_MS,
    };

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

  private sanitize(body: WaafiInitiateRequest): Record<string, unknown> {
    const sanitized = { ...body };
    if (sanitized.serviceParams?.apiKey) {
      sanitized.serviceParams.apiKey = '***REDACTED***';
    }
    return sanitized as unknown as Record<string, unknown>;
  }

  private get baseUrl(): string {
    return this.config.get<string>('waafi.baseUrl') ?? 'https://api.waafipay.net';
  }
  private get merchantUid(): string {
    return this.config.get<string>('waafi.merchantUid') ?? '';
  }
  private get apiUserId(): string {
    return this.config.get<string>('waafi.apiUserId') ?? '';
  }
  private get apiKey(): string {
    return this.config.get<string>('waafi.apiKey') ?? '';
  }
}
