<?php

declare(strict_types=1);

namespace App\Framework\Webhook\Processing;

use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Status;
use App\Framework\Logging\Logger;
use App\Framework\Router\Result\JsonResult;
use App\Framework\Webhook\Events\WebhookFailed;
use App\Framework\Webhook\Events\WebhookReceived;
use App\Framework\Webhook\Security\SignatureVerifier;
use App\Framework\Webhook\ValueObjects\WebhookPayload;
use App\Framework\Webhook\ValueObjects\WebhookProvider;
use Exception;

/**
 * Central webhook request handler using EventDispatcher
 * Handles incoming webhook requests, verifies signatures, and dispatches events
 */
final readonly class WebhookRequestHandler
{
    public function __construct(
        private SignatureVerifier $signatureVerifier,
        private EventDispatcher $eventDispatcher,
        private IdempotencyService $idempotencyService,
        private Logger $logger
    ) {
    }

    /**
     * Process incoming webhook request
     * Returns JSON response with processing status
     */
    public function handle(
        HttpRequest $request,
        WebhookProvider $provider,
        string $secret,
        array $allowedEvents = []
    ): HttpResponse {
        $requestId = $this->generateRequestId();
        $rawPayload = $request->getRawBody();

        try {
            // Step 1: Verify signature
            if (! $this->verifySignature($request, $rawPayload, $provider, $secret)) {
                $this->logger->warning('Webhook signature verification failed', [
                    'provider' => $provider->toString(),
                    'request_id' => $requestId,
                    'ip' => $request->server->getClientIp(),
                    'user_agent' => $request->server->getUserAgent()?->toString(),
                ]);

                return $this->errorResponse('Invalid signature', 401, $requestId);
            }

            // Step 2: Parse payload
            $payload = WebhookPayload::fromRaw($rawPayload);
            $eventType = $payload->getEventType();

            // Step 3: Check allowed events
            if (! empty($allowedEvents) && ! in_array($eventType, $allowedEvents, true)) {
                $this->logger->info('Webhook event not allowed', [
                    'provider' => $provider->toString(),
                    'event_type' => $eventType,
                    'allowed_events' => $allowedEvents,
                    'request_id' => $requestId,
                ]);

                return $this->successResponse('Event ignored', $requestId);
            }

            // Step 4: Check idempotency
            $webhookId = $payload->getWebhookId();
            if ($webhookId && $this->idempotencyService->isDuplicate($webhookId, $provider)) {
                $this->logger->info('Duplicate webhook request detected', [
                    'webhook_id' => $webhookId,
                    'provider' => $provider->toString(),
                    'request_id' => $requestId,
                ]);

                return $this->successResponse('Webhook already processed', $requestId);
            }

            // Step 5: Mark as processing
            if ($webhookId) {
                $this->idempotencyService->markProcessing($webhookId, $provider);
            }

            // Step 6: Create and dispatch webhook received event
            $webhookEvent = WebhookReceived::create(
                provider: $provider,
                payload: $payload,
                endpoint: $request->getPath(),
                eventType: $eventType
            );

            // Dispatch using EventDispatcher - returns array of handler results
            $results = $this->eventDispatcher->dispatch($webhookEvent);

            // Step 7: Mark as processed
            if ($webhookId) {
                $this->idempotencyService->markProcessed($webhookId, $provider);
            }

            $this->logger->info('Webhook processed successfully', [
                'provider' => $provider->toString(),
                'event_type' => $eventType,
                'webhook_id' => $webhookId,
                'request_id' => $requestId,
                'handlers_executed' => count($results),
                'processing_time' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'],
            ]);

            return $this->successResponse('Webhook processed', $requestId, [
                'event_type' => $eventType,
                'webhook_id' => $webhookId,
                'handlers_executed' => count($results),
            ]);

        } catch (Exception $e) {
            // Step 8: Handle processing errors
            if (isset($webhookId)) {
                $this->idempotencyService->markFailed($webhookId, $provider, $e->getMessage());
            }

            // Dispatch webhook failed event
            $failedEvent = WebhookFailed::create(
                provider: $provider,
                endpoint: $request->getPath(),
                error: $e->getMessage(),
                payload: isset($payload) ? $payload : null
            );

            $this->eventDispatcher->dispatch($failedEvent);

            $this->logger->error('Webhook processing failed', [
                'provider' => $provider->toString(),
                'request_id' => $requestId,
                'error' => $e->getMessage(),
                'webhook_id' => $webhookId ?? null,
            ]);

            return $this->errorResponse('Webhook processing failed', 500, $requestId);
        }
    }

    /**
     * Verify webhook signature using provider-specific verification
     */
    private function verifySignature(
        HttpRequest $request,
        string $payload,
        WebhookProvider $provider,
        string $secret
    ): bool {
        $signatureHeader = $this->signatureVerifier->getSignatureHeader($provider);
        $signature = $request->headers->get($signatureHeader);

        if (empty($signature)) {
            return false;
        }

        return $this->signatureVerifier->verify($payload, $signature, $secret, $provider);
    }

    /**
     * Generate unique request ID for tracking
     */
    private function generateRequestId(): string
    {
        return 'wh_' . bin2hex(random_bytes(8)) . '_' . time();
    }

    /**
     * Create success response
     */
    private function successResponse(string $message, string $requestId, array $data = []): JsonResult
    {
        return new JsonResult([
            'status' => 'success',
            'message' => $message,
            'request_id' => $requestId,
            'timestamp' => date('c'),
            ...$data,
        ]);
    }

    /**
     * Create error response
     */
    private function errorResponse(string $message, int $statusCode, string $requestId): JsonResult
    {
        return new JsonResult([
            'status' => 'error',
            'message' => $message,
            'request_id' => $requestId,
            'timestamp' => date('c'),
        ], Status::from($statusCode));
    }
}
