<?php

namespace App\Http\Controllers;

use App\Services\Addons\EventAddonWebhookService;
use App\Services\Checkout\CheckoutWebhookService;
use App\Services\Integrations\IntegrationWebhookRecorder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

class PaddleWebhookController extends Controller
{
    public function __construct(
        private readonly CheckoutWebhookService $webhooks,
        private readonly EventAddonWebhookService $addonWebhooks,
        private readonly IntegrationWebhookRecorder $recorder,
    ) {}

    public function handle(Request $request): JsonResponse
    {
        try {
            if (! $this->verify($request)) {
                Log::warning('Paddle webhook signature verification failed');

                return response()->json(['status' => 'invalid'], Response::HTTP_BAD_REQUEST);
            }

            $payload = $request->json()->all();

            if (! is_array($payload)) {
                return response()->json(['status' => 'ignored'], Response::HTTP_ACCEPTED);
            }

            $eventType = $payload['event_type'] ?? null;
            $eventId = $payload['event_id'] ?? $payload['id'] ?? data_get($payload, 'data.id');
            $webhookEvent = $this->recorder->recordReceived(
                'paddle',
                $eventId ? (string) $eventId : null,
                $eventType ? (string) $eventType : null,
            );
            $handled = false;

            $this->logDev('Paddle webhook received', [
                'event_type' => $eventType,
                'checkout_id' => data_get($payload, 'data.checkout_id'),
                'transaction_id' => data_get($payload, 'data.id'),
                'has_billing_signature' => (string) $request->headers->get('Paddle-Signature', '') !== '',
                'has_legacy_signature' => (string) $request->headers->get('Paddle-Webhook-Signature', '') !== '',
            ]);

            if ($eventType) {
                $handled = $this->webhooks->handlePaddleEvent($payload);
                $handled = $this->addonWebhooks->handle($payload) || $handled;
            }

            Log::info('Paddle webhook processed', [
                'event_type' => $eventType,
                'handled' => $handled,
            ]);

            $statusCode = $handled ? Response::HTTP_OK : Response::HTTP_ACCEPTED;
            $handled
                ? $this->recorder->markProcessed($webhookEvent, ['handled' => true])
                : $this->recorder->markIgnored($webhookEvent, ['handled' => false]);

            return response()->json([
                'status' => $handled ? 'processed' : 'ignored',
            ], $statusCode);
        } catch (\Throwable $exception) {
            $eventId = $this->captureWebhookException($exception);

            Log::error('Paddle webhook processing failed', [
                'message' => $exception->getMessage(),
                'event_type' => (string) $request->json('event_type'),
                'sentry_event_id' => $eventId,
            ]);

            $this->logDev('Paddle webhook error payload', $this->reducePayload($request->json()->all()));

            if (isset($webhookEvent)) {
                $this->recorder->markFailed($webhookEvent, $exception->getMessage());
            }

            return response()->json(['status' => 'error'], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    protected function verify(Request $request): bool
    {
        $secret = config('paddle.webhook_secret');

        if (! $secret) {
            // Allow processing in sandbox or when secret not configured
            return true;
        }

        $billingSignature = (string) $request->headers->get('Paddle-Signature', '');

        if ($billingSignature !== '') {
            $parts = $this->parseSignatureHeader($billingSignature);
            $timestamp = $parts['ts'] ?? null;
            $hash = $parts['h1'] ?? null;

            if (! $timestamp || ! $hash) {
                $this->logDev('Paddle webhook signature missing parts', [
                    'has_timestamp' => (bool) $timestamp,
                    'has_hash' => (bool) $hash,
                ]);

                return false;
            }

            $payload = $request->getContent();
            $expected = hash_hmac('sha256', $timestamp.':'.$payload, $secret);

            $valid = hash_equals($expected, $hash);
            if (! $valid) {
                $this->logDev('Paddle webhook signature mismatch (billing)', [
                    'timestamp' => $timestamp,
                ]);
            }

            return $valid;
        }

        $payload = $request->getContent();
        $signature = (string) $request->headers->get('Paddle-Webhook-Signature', '');

        if ($signature === '') {
            $this->logDev('Paddle webhook missing signature header', [
                'header' => 'Paddle-Webhook-Signature',
            ]);

            return false;
        }

        $expected = hash_hmac('sha256', $payload, $secret);

        $valid = hash_equals($expected, $signature);
        if (! $valid) {
            $this->logDev('Paddle webhook signature mismatch (legacy)', []);
        }

        return $valid;
    }

    /**
     * @return array<string, string>
     */
    protected function parseSignatureHeader(string $header): array
    {
        $parts = [];

        foreach (explode(',', $header) as $chunk) {
            $chunk = trim($chunk);
            if ($chunk === '' || ! str_contains($chunk, '=')) {
                continue;
            }

            [$key, $value] = array_map('trim', explode('=', $chunk, 2));
            if ($key !== '' && $value !== '') {
                $parts[$key] = $value;
            }
        }

        return $parts;
    }

    /**
     * @param  array<string, mixed>  $context
     */
    protected function logDev(string $message, array $context = []): void
    {
        if (! app()->environment('development')) {
            return;
        }

        Log::info('[PaddleWebhook] '.$message, $context);
    }

    /**
     * @return array<string, mixed>
     */
    protected function reducePayload(array $payload): array
    {
        return array_filter([
            'event_type' => $payload['event_type'] ?? null,
            'transaction_id' => data_get($payload, 'data.id'),
            'checkout_id' => data_get($payload, 'data.checkout_id'),
            'status' => data_get($payload, 'data.status'),
            'customer_id' => data_get($payload, 'data.customer_id'),
            'has_custom_data' => is_array(data_get($payload, 'data.custom_data')),
        ], static fn ($value) => $value !== null);
    }

    protected function captureWebhookException(\Throwable $exception): ?string
    {
        report($exception);

        if (! app()->bound('sentry') || empty(config('sentry.dsn'))) {
            return null;
        }

        try {
            $eventId = app('sentry')->captureException($exception);
        } catch (\Throwable) {
            return null;
        }

        return $eventId ? (string) $eventId : null;
    }
}
