import { Injectable, Logger, ServiceUnavailableException, UnauthorizedException } from '@nestjs/common';
import { createHmac, timingSafeEqual } from 'crypto';

/**
 * Verify an HMAC-SHA256 signature header (`hex` or `base64`) against
 * the raw webhook body using the shared secret. Throws `UnauthorizedException`
 * on mismatch. No-op (logs warning) if `secret` is empty — useful for dev.
 */
function verifyHmacSignature(
  rawBody: string,
  headerValue: string | undefined,
  secret: string,
  providerName: string,
): void {
  if (!secret) {
    // eslint-disable-next-line no-console
    console.warn(`[${providerName}] webhook signature not verified — secret missing`);
    return;
  }
  if (!headerValue) {
    throw new UnauthorizedException(`Missing webhook signature header for ${providerName}`);
  }
  const expectedHex = createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
  const expectedB64 = Buffer.from(expectedHex, 'hex').toString('base64');
  const received = headerValue.replace(/^sha256=/, '').trim();
  const toBuf = (s: string) => Buffer.from(s, s.length === expectedHex.length ? 'hex' : 'base64');
  try {
    const a = toBuf(received);
    const b = received.length === expectedHex.length ? Buffer.from(expectedHex, 'hex') : Buffer.from(expectedB64, 'base64');
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      throw new UnauthorizedException(`Invalid ${providerName} webhook signature`);
    }
  } catch (err) {
    if (err instanceof UnauthorizedException) throw err;
    throw new UnauthorizedException(`Invalid ${providerName} webhook signature encoding`);
  }
}


export interface PaymentIntent {
  providerRef: string;      // provider-side ID (e.g. YouCan transaction token)
  checkoutUrl: string;      // URL to redirect customer
  expiresAt?: Date;
}

export interface PaymentProvider {
  readonly code: string;    // "YOUCAN_PAY" | "STRIPE"
  readonly currencies: string[];

  /**
   * Create a hosted-checkout payment intent. Returns a URL the user is
   * redirected to; completion is confirmed via webhook.
   */
  createIntent(params: {
    paymentId: string;
    amount: number;
    currency: string;
    description: string;
    successUrl: string;
    failureUrl: string;
    metadata?: Record<string, string>;
  }): Promise<PaymentIntent>;

  /**
   * Verify + parse an incoming webhook. Returns the matched paymentId and
   * the final status reported by the provider.
   */
  parseWebhook(rawBody: string, headers: Record<string, string>): Promise<{
    paymentId: string;
    status: 'SUCCEEDED' | 'FAILED';
    providerRef: string;
  }>;
}

/**
 * Stub provider — simulates a YouCan Pay / Stripe flow end-to-end for
 * local/dev/testing. Returns a hosted-checkout URL that is actually a
 * dev-only endpoint able to auto-confirm payments (see payments.controller).
 */
@Injectable()
export class StubPaymentProvider implements PaymentProvider {
  readonly code = 'STUB';
  readonly currencies = ['MAD', 'EUR', 'USD'];
  private readonly logger = new Logger('StubPaymentProvider');

  async createIntent(params: any): Promise<PaymentIntent> {
    if (process.env.ENABLE_STUB_PROVIDERS !== 'true') {
      throw new ServiceUnavailableException('Stub payment provider is disabled');
    }
    const token = `stub_${params.paymentId.slice(0, 8)}_${Date.now()}`;
    this.logger.log(
      `[STUB] created intent paymentId=${params.paymentId} amount=${params.amount} ${params.currency}`,
    );
    return {
      providerRef: token,
      checkoutUrl: `${process.env.APP_BASE_URL ?? 'http://localhost:3000/api/v1'}/payments/stub-checkout/${token}`,
      expiresAt: new Date(Date.now() + 30 * 60 * 1000),
    };
  }

  async parseWebhook(rawBody: string, headers: Record<string, string> = {}) {
    if (process.env.ENABLE_STUB_PROVIDERS !== 'true') {
      throw new ServiceUnavailableException('Stub payment provider is disabled');
    }
    // Stub provider still verifies HMAC when WEBHOOK_SECRET is configured
    const secret = process.env.WEBHOOK_SECRET ?? '';
    verifyHmacSignature(rawBody, headers['x-signature'] ?? headers['x-webhook-signature'], secret, 'STUB');
    const data = JSON.parse(rawBody);
    return {
      paymentId: data.paymentId,
      status: data.status === 'paid' ? ('SUCCEEDED' as const) : ('FAILED' as const),
      providerRef: data.token ?? 'stub',
    };
  }
}

/**
 * Real YouCan Pay implementation skeleton. Until credentials and the live
 * tokenize call are configured, this fails explicitly instead of falling back
 * to a fake checkout.
 */
@Injectable()
export class YouCanPayProvider implements PaymentProvider {
  readonly code = 'YOUCAN_PAY';
  readonly currencies = ['MAD'];
  private readonly logger = new Logger('YouCanPayProvider');

  async createIntent(params: any): Promise<PaymentIntent> {
    const privateKey = process.env.YOUCAN_PAY_PRIVATE_KEY;
    if (!privateKey) {
      this.logger.error('YOUCAN_PAY_PRIVATE_KEY missing — online payment unavailable');
      throw new ServiceUnavailableException('YouCan Pay is not configured');
    }
    // TODO: POST https://pay.youcan.shop/api/tokenize with private_key, amount (in cents),
    // currency, order_id=paymentId, success_url, error_url. Then redirect to
    // https://youcanpay.com/pay/{token}
    throw new Error('YouCan Pay live call not implemented yet — populate API call here');
  }

  async parseWebhook(rawBody: string, headers: Record<string, string> = {}) {
    // Verify HMAC signature against YOUCAN_PAY_WEBHOOK_SECRET
    const secret = process.env.YOUCAN_PAY_WEBHOOK_SECRET ?? '';
    verifyHmacSignature(rawBody, headers['youcan-signature'] ?? headers['x-youcan-signature'], secret, 'YOUCAN_PAY');
    const data = JSON.parse(rawBody);
    return {
      paymentId: data.order_id,
      status: data.status === 'paid' ? ('SUCCEEDED' as const) : ('FAILED' as const),
      providerRef: data.token,
    };
  }
}
