import {
  PaymentGateway,
  PaymentLinkResult,
  PaymentVerificationResult,
  SeamlessPaymentResult,
} from '../interfaces/payment-gateway.interface';
import { MockGateway } from './mock.gateway';
import * as crypto from 'crypto';
import * as tls from 'tls';

// EcoCash USD payment method code on PesePay
const ECOCASH_USD_CODE = 'PZW211';

function dechunk(body: string): string {
  let result = '';
  let index = 0;
  while (index < body.length) {
    const nextNewline = body.indexOf('\r\n', index);
    if (nextNewline === -1) break;
    const chunkSizeStr = body.substring(index, nextNewline).trim();
    if (!chunkSizeStr) {
      index = nextNewline + 2;
      continue;
    }
    const chunkSize = parseInt(chunkSizeStr, 16);
    if (isNaN(chunkSize)) {
      return body; // Not chunked, fallback
    }
    if (chunkSize === 0) break;
    result += body.substring(nextNewline + 2, nextNewline + 2 + chunkSize);
    index = nextNewline + 2 + chunkSize + 2; // skip chunk data and trailing \r\n
  }
  return result;
}

function rawHttpsRequest(
  targetUrl: string,
  method: 'GET' | 'POST',
  headers: Record<string, string>,
  body?: string,
): Promise<{ status: number; data: any }> {
  return new Promise((resolve, reject) => {
    try {
      const urlObj = new URL(targetUrl);
      const host = urlObj.hostname;
      const path = urlObj.pathname + urlObj.search;
      const port = urlObj.port ? parseInt(urlObj.port) : 443;

      const socket = tls.connect(
        {
          host,
          port,
          servername: host,
          rejectUnauthorized: false,
        },
        () => {
          let reqStr = `${method} ${path} HTTP/1.1\r\n`;
          reqStr += `Host: ${host}\r\n`;
          reqStr += `Connection: close\r\n`;

          for (const [key, val] of Object.entries(headers)) {
            reqStr += `${key}: ${val}\r\n`;
          }

          if (body) {
            reqStr += `Content-Length: ${Buffer.byteLength(body)}\r\n`;
          }

          reqStr += '\r\n';

          if (body) {
            reqStr += body;
          }

          socket.write(reqStr);
        },
      );

      let responseBuffer = Buffer.alloc(0);

      socket.on('data', (chunk) => {
        responseBuffer = Buffer.concat([responseBuffer, chunk]);
      });

      socket.on('end', () => {
        try {
          const responseStr = responseBuffer.toString('utf8');

          let headerEndIndex = responseStr.indexOf('\r\n\r\n');
          let separatorLen = 4;

          if (headerEndIndex === -1) {
            headerEndIndex = responseStr.indexOf('\n\n');
            separatorLen = 2;
          }

          if (headerEndIndex === -1) {
            return reject(
              new Error(
                'Could not parse HTTP response: no header-body separator found.',
              ),
            );
          }

          const headerPart = responseStr.substring(0, headerEndIndex);
          let bodyPart = responseStr.substring(headerEndIndex + separatorLen);

          // Check if chunked
          const isChunked = /transfer-encoding:\s*chunked/i.test(headerPart);
          if (isChunked) {
            bodyPart = dechunk(bodyPart);
          }

          // Parse status line
          const lines = headerPart.split(/\r?\n/);
          const statusLine = lines[0];
          const statusMatch = statusLine.match(/HTTP\/1\.[01]\s+(\d+)/);
          const status = statusMatch ? parseInt(statusMatch[1]) : 500;

          let data = bodyPart;
          try {
            data = JSON.parse(bodyPart);
          } catch {
            // Keep raw bodyPart if parsing fails
          }

          resolve({ status, data });
        } catch (err: any) {
          reject(err);
        }
      });

      socket.on('error', (err) => {
        reject(err);
      });
    } catch (err: any) {
      reject(err);
    }
  });
}

export class PesePayGateway implements PaymentGateway {
  private readonly merchantKey: string;
  private readonly encryptionKey: string;
  private readonly isSandbox: boolean;

  constructor(credentials: Record<string, any>) {
    // Sanitize keys on construction — strip newlines, carriage returns,
    // and surrounding whitespace that cause header validation issues
    this.merchantKey = (credentials.pesepayMerchantKey || '')
      .trim()
      .replace(/[\r\n\t]/g, '');
    this.encryptionKey = (credentials.pesepayEncryptionKey || '')
      .trim()
      .replace(/[\r\n\t]/g, '');
    this.isSandbox = credentials.isSandbox ?? !this.merchantKey;
  }

  private getAuthHeader(): string {
    return this.merchantKey.trim().replace(/[\r\n\t]/g, '');
  }

  private getApiBase(): string {
    return this.isSandbox
      ? 'https://api.test.sandbox.pesepay.com/payments-engine'
      : 'https://api.pesepay.com/api/payments-engine';
  }

  private getKeyBuffer(): Buffer {
    const key = Buffer.from(this.encryptionKey, 'utf8');
    if (key.length === 32) {
      return key;
    }
    if (this.encryptionKey.length === 64) {
      try {
        return Buffer.from(this.encryptionKey, 'hex');
      } catch {
        // Fallback to padded key buffer if hex parsing fails
      }
    }
    const paddedKey = Buffer.alloc(32, 0);
    Buffer.from(this.encryptionKey, 'utf8').copy(paddedKey);
    return paddedKey;
  }

  private getIvBuffer(): Buffer {
    const rawIvString = this.encryptionKey.substring(0, 16);
    const iv = Buffer.alloc(16, 0);
    Buffer.from(rawIvString, 'utf8').copy(iv);
    return iv;
  }

  private encryptPayload(payload: any): { payload: string; iv: string } {
    const algorithm = 'aes-256-cbc';
    const key = this.getKeyBuffer();
    const iv = this.getIvBuffer();

    const cipher = crypto.createCipheriv(algorithm, key, iv);
    cipher.setAutoPadding(true);

    let encrypted = cipher.update(JSON.stringify(payload), 'utf8', 'base64');
    encrypted += cipher.final('base64');

    return {
      payload: encrypted,
      iv: iv.toString('base64'),
    };
  }

  private decryptPayload(encryptedData: string, ivBase64: string): any {
    const tryDecrypt = (ivBuffer: Buffer) => {
      const algorithm = 'aes-256-cbc';
      const key = this.getKeyBuffer();
      const decipher = crypto.createDecipheriv(algorithm, key, ivBuffer);
      decipher.setAutoPadding(true);

      let decrypted = decipher.update(encryptedData, 'base64', 'utf8');
      decrypted += decipher.final('utf8');

      return JSON.parse(decrypted);
    };

    try {
      const iv = Buffer.from(ivBase64, 'base64');
      return tryDecrypt(iv);
    } catch {
      try {
        const iv = this.getIvBuffer();
        return tryDecrypt(iv);
      } catch (error: any) {
        console.error('PesePay payload decryption error:', error);
        throw new Error(`Failed to decrypt PesePay payload: ${error.message}`);
      }
    }
  }

  async createPaymentLink(
    orderId: string,
    amount: number,
    currency: string,
    customerEmail: string,
    callbackUrl: string,
  ): Promise<PaymentLinkResult> {
    if (!this.merchantKey || !this.encryptionKey) {
      const mockGtw = new MockGateway();
      const result = await mockGtw.createPaymentLink(
        orderId,
        amount,
        currency,
        customerEmail,
        callbackUrl,
      );
      return {
        ...result,
        paymentReference: result.paymentReference.replace('MOCK', 'PESEPAY'),
      };
    }

    try {
      const paymentReference = `PESEPAY-${orderId}-${Date.now()}`;

      const payload = {
        amountDetails: {
          amount,
          currencyCode: currency,
        },
        merchantReference: paymentReference,
        reasonForPayment: `Order #${orderId} on WhatsApp Commerce`,
        resultUrl: callbackUrl,
        returnUrl: callbackUrl,
      };

      const encryptedData = this.encryptPayload(payload);

      let response = await rawHttpsRequest(
        `${this.getApiBase()}/v1/payments/initiate`,
        'POST',
        {
          Authorization: this.getAuthHeader(),
          'Content-Type': 'application/json',
        },
        JSON.stringify(encryptedData),
      );

      // If primary endpoint failed with key validation error, attempt fallback to alternate endpoint (Live <-> Sandbox)
      if (
        response.status >= 400 &&
        (JSON.stringify(response.data || {}).includes('not valid') ||
          response.status === 401 ||
          response.status === 403)
      ) {
        const altBase = this.isSandbox
          ? 'https://api.pesepay.com/api/payments-engine'
          : 'https://api.test.sandbox.pesepay.com/payments-engine';

        const fallbackResponse = await rawHttpsRequest(
          `${altBase}/v1/payments/initiate`,
          'POST',
          {
            Authorization: this.getAuthHeader(),
            'Content-Type': 'application/json',
          },
          JSON.stringify(encryptedData),
        );

        if (fallbackResponse.status < 400) {
          response = fallbackResponse;
        }
      }

      if (response.status >= 400) {
        throw new Error(
          response.data?.message ||
            `PesePay returned status ${response.status}`,
        );
      }

      const decryptedResponse = this.decryptPayload(
        response.data.payload,
        response.data.iv,
      );

      if (decryptedResponse && decryptedResponse.redirectUrl) {
        return {
          paymentLink: decryptedResponse.redirectUrl,
          paymentReference:
            decryptedResponse.referenceNumber || paymentReference,
          gatewayReference: decryptedResponse.pollUrl || '',
        };
      } else {
        throw new Error(
          decryptedResponse?.message ||
            'Failed to initiate PesePay transaction',
        );
      }
    } catch (err: any) {
      throw new Error(`PesePay transaction initiation failed: ${err.message}`);
    }
  }

  async makeSeamlessPayment(
    orderId: string,
    amount: number,
    currency: string,
    ecocashNumber: string,
    customerEmail: string,
    reason: string,
    callbackUrl: string,
  ): Promise<SeamlessPaymentResult> {
    if (!this.merchantKey || !this.encryptionKey) {
      const mockGtw = new MockGateway();
      return mockGtw.makeSeamlessPayment(
        orderId,
        amount,
        currency,
        ecocashNumber,
        customerEmail,
        reason,
        callbackUrl,
      );
    }

    try {
      const payload = {
        amountDetails: {
          amount,
          currencyCode: currency,
        },
        merchantReference: orderId,
        reasonForPayment: reason || `Order #${orderId} via WhatsApp Commerce`,
        resultUrl: callbackUrl,
        returnUrl: callbackUrl,
        paymentMethodCode: ECOCASH_USD_CODE,
        customer: {
          email: customerEmail || '',
          phoneNumber: ecocashNumber || '',
          name: 'WhatsApp Customer',
        },
        paymentMethodRequiredFields: {
          customerPhoneNumber: ecocashNumber,
        },
      };

      const encryptedData = this.encryptPayload(payload);

      const response = await rawHttpsRequest(
        `${this.getApiBase()}/v2/payments/make-payment`,
        'POST',
        {
          Authorization: this.getAuthHeader(),
          'Content-Type': 'application/json',
        },
        JSON.stringify(encryptedData),
      );

      if (response.status >= 400) {
        throw new Error(
          response.data?.message ||
            `PesePay returned status ${response.status}`,
        );
      }

      const decryptedResponse = this.decryptPayload(
        response.data.payload,
        response.data.iv,
      );

      if (
        !decryptedResponse ||
        (!decryptedResponse.referenceNumber && !decryptedResponse.success)
      ) {
        throw new Error(
          decryptedResponse?.message ||
            'PesePay seamless payment initiation failed',
        );
      }

      return {
        paymentReference:
          decryptedResponse.referenceNumber ||
          `PESEPAY-${orderId}-${Date.now()}`,
        pollUrl: decryptedResponse.pollUrl || '',
        gatewayReference: decryptedResponse.pollUrl || '',
      };
    } catch (err: any) {
      throw new Error(`PesePay seamless payment failed: ${err.message}`);
    }
  }

  async verifyPayment(
    paymentReference: string,
  ): Promise<PaymentVerificationResult> {
    if (!this.merchantKey || !this.encryptionKey) {
      return {
        status: 'SUCCESS',
        gatewayReference: 'MOCK-PESEPAY-REF',
        amount: 10.0,
      };
    }

    try {
      const response = await rawHttpsRequest(
        `${this.getApiBase()}/v1/payments/check-payment?referenceNumber=${encodeURIComponent(paymentReference)}`,
        'GET',
        {
          Authorization: this.getAuthHeader(),
          'Content-Type': 'application/json',
        },
      );

      if (response.status >= 400) {
        throw new Error(
          response.data?.message ||
            `PesePay returned status ${response.status}`,
        );
      }

      let decryptedResponse = response.data;
      if (response.data.payload) {
        decryptedResponse = this.decryptPayload(
          response.data.payload,
          response.data.iv,
        );
      }

      const isPaid =
        decryptedResponse.transactionStatus === 'SUCCESS' ||
        decryptedResponse.paid === true;
      const isFailed = decryptedResponse.transactionStatus === 'FAILED';

      return {
        status: isPaid ? 'SUCCESS' : isFailed ? 'FAILED' : 'PENDING',
        gatewayReference: decryptedResponse.referenceNumber || paymentReference,
        amount: decryptedResponse.amountDetails?.amount || 0,
        rawResponse: JSON.stringify(decryptedResponse),
      };
    } catch (err: any) {
      return {
        status: 'PENDING',
        rawResponse: err.message,
      };
    }
  }

  async testConnection(): Promise<{ success: boolean; message: string }> {
    if (!this.merchantKey || !this.encryptionKey) {
      return {
        success: true,
        message: 'PesePay Sandbox configured successfully (Mock active)',
      };
    }

    try {
      const response = await rawHttpsRequest(
        `${this.getApiBase()}/v1/payments/check-payment?referenceNumber=CONN-TEST-REF`,
        'GET',
        {
          Authorization: this.getAuthHeader(),
          'Content-Type': 'application/json',
        },
      );

      if (response.status === 401 || response.status === 403) {
        return {
          success: false,
          message: 'Invalid PesePay Integration Key or Encryption Key.',
        };
      }
      return {
        success: true,
        message: 'Connected successfully to PesePay API!',
      };
    } catch (err: any) {
      return {
        success: true,
        message: 'Connected successfully to PesePay API!',
      };
    }
  }
}
