import { createHmac, timingSafeEqual } from 'node:crypto';
import Stripe from 'stripe';
import type { PaymentProvider } from '../domain/ports.js';
import type { PaymentEvent, PaymentIntent } from '../domain/payment.js';
import { InvalidWebhookSignatureError } from '../domain/errors.js';
import { AppError } from '../../../shared/errors.js';

interface StripeLikePayload {
  id?: string;
  type?: string;
  data?: { object?: { id?: string; metadata?: { order_id?: string; amount?: number } } };
}

const SIGNATURE_HEADER = 'stripe-signature';
const TOLERANCE_SECONDS = 300;

/**
 * StripePaymentProvider keeps all Stripe knowledge inside the adapter.
 * Domain code never imports Stripe.
 */
export class StripePaymentProvider implements PaymentProvider {
  private stripe: Stripe | null = null;
  constructor(
    private readonly webhookSecret: string,
    private readonly stripeSecretKey?: string,
  ) {
    if (stripeSecretKey) {
      this.stripe = new Stripe(stripeSecretKey);
    }
  }

  async refund(paymentIntentId: string): Promise<void> {
    if (!this.stripe)
      throw new AppError(500, 'STRIPE_NOT_CONFIGURED', 'Stripe is not configured for refunds');
    await this.stripe.refunds.create({ payment_intent: paymentIntentId });
  }

  createIntent(_input: {
    orderId: string;
    amountCents: number;
    currency: 'EUR';
  }): Promise<PaymentIntent> {
    // Real Stripe SDK would be used here; for v1 we provide a stub intent.
    return Promise.resolve({
      id: 'pi_stub',
      reference: 'pi_stub',
      status: 'requires_payment',
    });
  }

  verifyWebhook(rawBody: string, signature: string | undefined): PaymentEvent {
    if (!signature || !signature.startsWith('t=')) {
      throw new InvalidWebhookSignatureError();
    }
    const parts = signature.split(',').reduce<Record<string, string>>((acc, segment) => {
      const [key, value] = segment.split('=');
      if (key && value !== undefined) acc[key] = value;
      return acc;
    }, {});
    const timestamp = parts['t'];
    const receivedV1 = parts['v1'];
    if (!timestamp || !receivedV1) {
      throw new InvalidWebhookSignatureError();
    }
    const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
    if (!Number.isFinite(ageSeconds) || ageSeconds > TOLERANCE_SECONDS) {
      throw new InvalidWebhookSignatureError();
    }
    const signedPayload = `${timestamp}.${rawBody}`;
    const expectedV1 = createHmac('sha256', this.webhookSecret).update(signedPayload).digest('hex');
    const expectedBuffer = Buffer.from(expectedV1, 'hex');
    const receivedBuffer = Buffer.from(receivedV1, 'hex');
    if (
      expectedBuffer.length !== receivedBuffer.length ||
      !timingSafeEqual(expectedBuffer, receivedBuffer)
    ) {
      throw new InvalidWebhookSignatureError();
    }
    const parsed = JSON.parse(rawBody) as StripeLikePayload;
    if (!parsed.id || !parsed.type) throw new InvalidWebhookSignatureError();
    return this.toEvent(parsed);
  }

  private toEvent(parsed: StripeLikePayload): PaymentEvent {
    const dataObject = parsed.data?.object ?? {};
    const orderId =
      typeof dataObject.metadata?.order_id === 'string' ? dataObject.metadata.order_id : null;
    const amountCents =
      typeof dataObject.metadata?.amount === 'number' ? dataObject.metadata.amount : 0;
    const providerPaymentId = typeof dataObject.id === 'string' ? dataObject.id : null;
    const providerEventId = parsed.id ?? '';
    if (!providerEventId) throw new InvalidWebhookSignatureError();
    switch (parsed.type) {
      case 'payment_intent.succeeded':
        return {
          providerEventId,
          providerPaymentId,
          orderId,
          amountCents,
          currency: 'EUR',
          status: 'succeeded',
          type: 'PaymentSucceeded',
          raw: parsed as unknown as Record<string, unknown>,
        };
      case 'payment_intent.payment_failed':
        return {
          providerEventId,
          providerPaymentId,
          orderId,
          amountCents,
          currency: 'EUR',
          status: 'failed',
          type: 'PaymentFailed',
          raw: parsed as unknown as Record<string, unknown>,
        };
      case 'charge.refunded':
        return {
          providerEventId,
          providerPaymentId,
          orderId,
          amountCents,
          currency: 'EUR',
          status: 'refunded',
          type: 'PaymentRefunded',
          raw: parsed as unknown as Record<string, unknown>,
        };
      case 'charge.dispute.created':
        return {
          providerEventId,
          providerPaymentId,
          orderId,
          amountCents,
          currency: 'EUR',
          status: 'chargeback',
          type: 'ChargebackCreated',
          raw: parsed as unknown as Record<string, unknown>,
        };
      default:
        throw new InvalidWebhookSignatureError();
    }
  }
}

export { SIGNATURE_HEADER };
