<?php

namespace FleekDash\Integrations\Core;

defined('ABSPATH') || exit;

/**
 * Outgoing webhook queue, HTTP delivery, and delivery/failure logging.
 */
final class WebhookOutgoingProcessor
{
    private const QUEUE_OPTION = 'fleekdash_webhook_queue';

    public function enqueue(string $subscriptionId, string $event, array $data): void
    {
        $webhook = [
            'id' => wp_generate_uuid4(),
            'subscription_id' => $subscriptionId,
            'event' => $event,
            'data' => $data,
            'attempts' => 0,
            'created_at' => current_time('mysql'),
            'next_attempt' => current_time('mysql'),
        ];

        $queue = get_option(self::QUEUE_OPTION, []);
        if (!is_array($queue)) {
            $queue = [];
        }
        $queue[] = $webhook;
        update_option(self::QUEUE_OPTION, $queue);

        if (!wp_next_scheduled('fleekdash_process_webhooks')) {
            wp_schedule_single_event(time() + 10, 'fleekdash_process_webhooks');
        }
    }

    /**
     * @param array<string, array<string, mixed>> $subscriptions
     */
    public function processPending(array $subscriptions): void
    {
        $queue = get_option(self::QUEUE_OPTION, []);
        $queue = is_array($queue) ? $queue : [];
        if ($queue === []) {
            return;
        }

        $remaining = $this->drainQueue($queue, $subscriptions);
        update_option(self::QUEUE_OPTION, $remaining);

        if ($remaining !== [] && !wp_next_scheduled('fleekdash_process_webhooks')) {
            wp_schedule_single_event(time() + 300, 'fleekdash_process_webhooks');
        }
    }

    /**
     * @param list<array<string, mixed>>          $queue
     * @param array<string, array<string, mixed>> $subscriptions
     *
     * @return list<array<string, mixed>>
     */
    private function drainQueue(array $queue, array $subscriptions): array
    {
        $remaining = [];
        foreach ($queue as $webhook) {
            $nextAttempt = strtotime((string) ($webhook['next_attempt'] ?? ''));
            if ($nextAttempt > time()) {
                $remaining[] = $webhook;
                continue;
            }

            $subscription = $subscriptions[$webhook['subscription_id'] ?? ''] ?? null;
            if (!is_array($subscription)) {
                continue;
            }

            $result = $this->sendWebhook(
                $subscription,
                (string) ($webhook['event'] ?? ''),
                is_array($webhook['data'] ?? null) ? $webhook['data'] : [],
            );

            if ($result['success']) {
                $this->logDelivery($webhook, $result);
                continue;
            }

            $webhook['attempts'] = (int) ($webhook['attempts'] ?? 0) + 1;
            $retryCap = (int) ($subscription['retry_count'] ?? 3);
            if ($webhook['attempts'] >= $retryCap) {
                $this->logFailure($webhook, $result);
                continue;
            }

            $delay = (int) pow(2, $webhook['attempts']) * 60;
            $webhook['next_attempt'] = date('Y-m-d H:i:s', time() + $delay);
            $remaining[] = $webhook;
        }

        return $remaining;
    }

    /**
     * @param array<string, mixed> $subscription
     *
     * @return array<string, mixed>
     */
    private function sendWebhook(array $subscription, string $event, array $data): array
    {
        $payload = [
            'event' => $event,
            'data' => $data,
            'timestamp' => time(),
            'id' => wp_generate_uuid4(),
        ];

        $body = json_encode($payload);
        if ($body === false) {
            return [
                'success' => false,
                'error' => 'Failed to encode webhook payload',
                'code' => 0,
            ];
        }

        $secret = (string) ($subscription['secret'] ?? '');
        $signature = hash_hmac('sha256', $body, $secret);

        $headers = array_merge([
            'Content-Type' => 'application/json',
            'User-Agent' => 'FleekDash-Webhooks/1.0',
            'X-FleekDash-Event' => $event,
            'X-FleekDash-Signature' => 'sha256=' . $signature,
            'X-FleekDash-Delivery' => wp_generate_uuid4(),
        ], is_array($subscription['headers'] ?? null) ? $subscription['headers'] : []);

        $response = wp_remote_post((string) ($subscription['url'] ?? ''), [
            'headers' => $headers,
            'body' => $body,
            'timeout' => (int) ($subscription['timeout'] ?? 30),
            'blocking' => true,
        ]);

        if (is_wp_error($response)) {
            return [
                'success' => false,
                'error' => $response->get_error_message(),
                'code' => 0,
            ];
        }

        $code = wp_remote_retrieve_response_code($response);
        $success = $code >= 200 && $code < 300;

        return [
            'success' => $success,
            'code' => $code,
            'response' => wp_remote_retrieve_body($response),
            'headers' => wp_remote_retrieve_headers($response),
        ];
    }

    /**
     * @param array<string, mixed> $webhook
     * @param array<string, mixed> $result
     */
    private function logDelivery(array $webhook, array $result): void
    {
        WebhookLogRepository::append([
            'type' => 'delivery',
            'webhook_id' => $webhook['id'],
            'event' => $webhook['event'],
            'attempts' => $webhook['attempts'],
            'response_code' => $result['code'],
            'timestamp' => current_time('mysql'),
        ]);
    }

    /**
     * @param array<string, mixed> $webhook
     * @param array<string, mixed> $result
     */
    private function logFailure(array $webhook, array $result): void
    {
        WebhookLogRepository::append([
            'type' => 'failure',
            'webhook_id' => $webhook['id'],
            'event' => $webhook['event'],
            'attempts' => $webhook['attempts'],
            'error' => $result['error'] ?? 'Unknown error',
            'timestamp' => current_time('mysql'),
        ]);
    }
}
