<?php

declare(strict_types=1);

namespace Origin\Webhook;

use Origin\Engine\EngineClient;
use Origin\Payment\PaymentBridge;
use RuntimeException;
use InvalidArgumentException;
use JsonException;

class WebhookHandler
{
    private EngineClient $engine;
    private PaymentBridge $paymentBridge;
    private array $config;

    public function __construct(EngineClient $engine, PaymentBridge $paymentBridge, array $config)
    {
        $this->engine = $engine;
        $this->paymentBridge = $paymentBridge;
        $this->config = $config;
    }

    public function handleStripeWebhook(string $payload, string $sigHeader): array
    {
        try {
            $result = $this->paymentBridge->validateStripeWebhook($payload, $sigHeader);
        } catch (RuntimeException $e) {
            http_response_code(401);
            throw $e;
        }

        $event = $result['event'];
        $eventType = $event['type'] ?? 'unknown';

        match ($eventType) {
            'checkout.session.completed' => $this->handleCheckoutCompleted($event),
            'payment_intent.succeeded' => $this->handlePaymentSucceeded($event),
            'payment_intent.payment_failed' => $this->handlePaymentFailed($event),
            'charge.refunded' => $this->handleChargeRefunded($event),
            'charge.dispute.created' => $this->handleDisputeCreated($event),
            default => $this->handleUnhandledEvent($event),
        };

        return ['received' => true, 'type' => $eventType];
    }

    public function handleXMRWebhook(array $payload): array
    {
        $required = ['tx_hash', 'subaddress', 'amount', 'confirmations'];
        foreach ($required as $field) {
            if (empty($payload[$field])) {
                throw new InvalidArgumentException("Missing required field: {$field}");
            }
        }

        $txHash = $payload['tx_hash'];
        $subaddress = $payload['subaddress'];
        $amountAtomic = (int)$payload['amount'];
        $confirmations = (int)$payload['confirmations'];
        $requiredConfirmations = $this->config['xmr']['confirmations_required'] ?? 10;

        $this->engine->submitArtifact('xmr_payment_update', [
            'tx_hash' => $txHash,
            'subaddress' => $subaddress,
            'amount_atomic' => $amountAtomic,
            'amount_xmr' => $amountAtomic / 1e12,
            'confirmations' => $confirmations,
            'confirmed' => $confirmations >= $requiredConfirmations,
            'received_at' => date('c'),
        ]);

        return [
            'processed' => true,
            'subaddress' => $subaddress,
            'confirmations' => $confirmations,
            'confirmed' => $confirmations >= $requiredConfirmations,
        ];
    }

    public function handleCanadaPostWebhook(array $payload): array
    {
        $required = ['tracking_number', 'event_type'];
        foreach ($required as $field) {
            if (empty($payload[$field])) {
                throw new InvalidArgumentException("Missing required field: {$field}");
            }
        }

        $trackingNumber = $payload['tracking_number'];
        $eventType = $payload['event_type'];

        $statusMap = [
            'ITEM_SHIPPED' => 'shipped',
            'ITEM_IN_TRANSIT' => 'in_transit',
            'ITEM_OUT_FOR_DELIVERY' => 'out_for_delivery',
            'ITEM_DELIVERED' => 'delivered',
            'DELIVERY_EXCEPTION' => 'exception',
            'RETURN_TO_SENDER' => 'returned',
        ];

        $normalizedStatus = $statusMap[$eventType] ?? 'unknown';

        $this->engine->submitArtifact('fulfillment_update', [
            'tracking_number' => $trackingNumber,
            'carrier' => 'canadapost',
            'status' => $normalizedStatus,
            'raw_event' => $eventType,
            'event_location' => $payload['event_location'] ?? null,
            'event_date' => $payload['event_date'] ?? date('c'),
            'order_ref' => $payload['order_ref'] ?? null,
            'updated_at' => date('c'),
        ]);

        return [
            'processed' => true,
            'tracking_number' => $trackingNumber,
            'status' => $normalizedStatus,
        ];
    }

    public function handleSecurityEventWebhook(array $payload): array
    {
        $required = ['severity', 'type', 'source_ip'];
        foreach ($required as $field) {
            if (empty($payload[$field])) {
                throw new InvalidArgumentException("Missing required field: {$field}");
            }
        }

        $severity = strtolower($payload['severity']);
        $validSeverities = ['low', 'medium', 'high', 'critical'];
        if (!in_array($severity, $validSeverities, true)) {
            throw new InvalidArgumentException('Invalid severity level');
        }

        $event = [
            'severity' => $severity,
            'type' => $payload['type'],
            'source' => $payload['source_ip'],
            'description' => $payload['description'] ?? '',
            'user_agent' => $payload['user_agent'] ?? '',
            'path' => $payload['path'] ?? '',
            'timestamp' => $payload['timestamp'] ?? date('c'),
            'node' => '669427',
        ];

        $this->engine->reportSecurityEvent($event);

        if (in_array($severity, ['high', 'critical'], true)) {
            $this->engine->submitArtifact('security_alert', [
                'level' => $severity,
                'event' => $event,
                'requires_immediate' => $severity === 'critical',
            ]);
        }

        return ['received' => true, 'severity' => $severity];
    }

    public function handleRawWebhook(string $source, string $payload, array $headers = []): array
    {
        $source = strtolower($source);

        match ($source) {
            'stripe' => $this->validateSourceIp($headers['REMOTE_ADDR'] ?? '', ['3.18.12.63', '3.130.192.231', '13.235.14.237', '13.235.122.149', '35.154.171.200', '52.15.183.38', '54.88.130.119', '54.187.174.169', '54.187.205.235', '54.187.216.72']),
            'canadapost' => $this->validateSourceIp($headers['REMOTE_ADDR'] ?? '', ['206.191.4.0/24']),
            default => true,
        };

        switch ($source) {
            case 'stripe':
                return $this->handleStripeWebhook($payload, $headers['HTTP_STRIPE_SIGNATURE'] ?? '');
            case 'xmr':
                $decoded = json_decode($payload, true);
                if (!is_array($decoded)) {
                    throw new InvalidArgumentException('Invalid JSON payload');
                }
                return $this->handleXMRWebhook($decoded);
            case 'canadapost':
                $decoded = json_decode($payload, true);
                if (!is_array($decoded)) {
                    throw new InvalidArgumentException('Invalid JSON payload');
                }
                return $this->handleCanadaPostWebhook($decoded);
            case 'security':
                $decoded = json_decode($payload, true);
                if (!is_array($decoded)) {
                    throw new InvalidArgumentException('Invalid JSON payload');
                }
                return $this->handleSecurityEventWebhook($decoded);
            default:
                throw new InvalidArgumentException('Unknown webhook source: ' . $source);
        }
    }

    private function handleCheckoutCompleted(array $event): void
    {
        $session = $event['data']['object'] ?? [];
        $this->engine->submitArtifact('stripe_checkout_completed', [
            'session_id' => $session['id'] ?? '',
            'payment_intent' => $session['payment_intent'] ?? '',
            'customer' => $session['customer'] ?? '',
            'amount_total' => $session['amount_total'] ?? 0,
            'currency' => $session['currency'] ?? '',
            'metadata' => $session['metadata'] ?? [],
            'payment_status' => $session['payment_status'] ?? '',
            'created' => $session['created'] ?? time(),
        ]);
    }

    private function handlePaymentSucceeded(array $event): void
    {
        $intent = $event['data']['object'] ?? [];
        $this->engine->submitArtifact('stripe_payment_succeeded', [
            'payment_intent_id' => $intent['id'] ?? '',
            'amount' => $intent['amount'] ?? 0,
            'currency' => $intent['currency'] ?? '',
            'charges' => $intent['charges']['data'] ?? [],
            'metadata' => $intent['metadata'] ?? [],
        ]);
    }

    private function handlePaymentFailed(array $event): void
    {
        $intent = $event['data']['object'] ?? [];
        $lastError = $intent['last_payment_error'] ?? [];
        $this->engine->submitArtifact('stripe_payment_failed', [
            'payment_intent_id' => $intent['id'] ?? '',
            'amount' => $intent['amount'] ?? 0,
            'error_code' => $lastError['code'] ?? 'unknown',
            'error_message' => $lastError['message'] ?? 'Unknown error',
            'decline_code' => $lastError['decline_code'] ?? null,
            'metadata' => $intent['metadata'] ?? [],
        ]);
    }

    private function handleChargeRefunded(array $event): void
    {
        $charge = $event['data']['object'] ?? [];
        $this->engine->submitArtifact('stripe_charge_refunded', [
            'charge_id' => $charge['id'] ?? '',
            'amount_refunded' => $charge['amount_refunded'] ?? 0,
            'reason' => $charge['refunds']['data'][0]['reason'] ?? 'unknown',
            'metadata' => $charge['metadata'] ?? [],
        ]);
    }

    private function handleDisputeCreated(array $event): void
    {
        $dispute = $event['data']['object'] ?? [];
        $this->engine->reportSecurityEvent([
            'severity' => 'high',
            'type' => 'stripe_dispute',
            'source' => 'stripe_webhook',
            'description' => 'Chargeback initiated: ' . ($dispute['reason'] ?? 'unknown'),
            'dispute_id' => $dispute['id'] ?? '',
            'charge_id' => $dispute['charge'] ?? '',
            'amount' => $dispute['amount'] ?? 0,
        ]);
    }

    private function handleUnhandledEvent(array $event): void
    {
        $this->engine->submitArtifact('stripe_unhandled_event', [
            'type' => $event['type'] ?? 'unknown',
            'event_id' => $event['id'] ?? '',
            'received_at' => date('c'),
        ]);
    }

    private function validateSourceIp(string $clientIp, array $allowedRanges): void
    {
        if (empty($clientIp) || $clientIp === '127.0.0.1') {
            return;
        }

        foreach ($allowedRanges as $range) {
            if ($this->ipInRange($clientIp, $range)) {
                return;
            }
        }

        throw new RuntimeException('Webhook source IP not authorized: ' . $clientIp);
    }

    private function ipInRange(string $ip, string $range): bool
    {
        if (str_contains($range, '/')) {
            [$subnet, $bits] = explode('/', $range);
            $mask = -1 << (32 - (int)$bits);
            return (ip2long($ip) & $mask) === (ip2long($subnet) & $mask);
        }
        return $ip === $range;
    }
}
