<?php

namespace GriffinForms\Includes\Api;

use GriffinForms\Config;
use GriffinForms\Includes\Pipelines\JobRepository;
use GriffinForms\Includes\Security\Capabilities;
use GriffinForms\Settings;

/**
 * Outbound webhook delivery contract for companion-app events.
 */
class WebhookDelivery
{
    private const DEVICE_OPTION_PREFIX = 'api_device_registry_user_';
    public const OPTION_WEBHOOK_ENABLED = 'api_webhook_submission_created_enabled';
    public const OPTION_WEBHOOK_URL = 'api_webhook_submission_created_url';
    public const OPTION_WEBHOOK_SECRET = 'api_webhook_submission_created_secret';
    public const OPTION_WEBHOOK_TIMEOUT = 'api_webhook_submission_created_timeout';

    public static function registerHooks(): void
    {
        add_action('init', [static::class, 'seedDefaultOptions'], 20);
        add_action('griffinforms_post_submission_after_pipeline', [static::class, 'queueSubmissionCreated'], 20, 1);
    }

    public static function seedDefaultOptions(): void
    {
        $settings = Settings::getInstance();

        if ($settings->getOption(static::OPTION_WEBHOOK_ENABLED) === null) {
            $settings->updateOption(static::OPTION_WEBHOOK_ENABLED, 0, 'capabilitymatrix');
        }

        if ($settings->getOption(static::OPTION_WEBHOOK_URL) === null) {
            $settings->updateOption(static::OPTION_WEBHOOK_URL, '', 'capabilitymatrix');
        }

        if ($settings->getOption(static::OPTION_WEBHOOK_SECRET) === null) {
            $settings->updateOption(static::OPTION_WEBHOOK_SECRET, '', 'capabilitymatrix');
        }

        if ($settings->getOption(static::OPTION_WEBHOOK_TIMEOUT) === null) {
            $settings->updateOption(static::OPTION_WEBHOOK_TIMEOUT, 10, 'capabilitymatrix');
        }
    }

    /**
     * Queue replay-safe submission.created event for outbound delivery.
     *
     * @param array $context Pipeline context.
     */
    public static function queueSubmissionCreated(array $context): void
    {
        $settings = Settings::getInstance();
        $submission_id = absint($context['submission_id'] ?? 0);
        $form_id = absint($context['form_id'] ?? 0);
        $enabled = (int) $settings->getOption(static::OPTION_WEBHOOK_ENABLED, 0);
        if ($enabled !== 1) {
            static::logQueueAudit('webhook_submission_created_queue_skipped', 'skipped', $submission_id, $form_id, [
                'reason' => 'webhook_disabled',
            ]);
            return;
        }

        $target_url = esc_url_raw((string) $settings->getOption(static::OPTION_WEBHOOK_URL, ''));
        $secret = (string) $settings->getOption(static::OPTION_WEBHOOK_SECRET, '');
        $timeout = (int) $settings->getOption(static::OPTION_WEBHOOK_TIMEOUT, 10);
        $timeout = max(3, min(30, $timeout));

        if ($target_url === '' || !wp_http_validate_url($target_url) || $secret === '') {
            static::logQueueAudit('webhook_submission_created_queue_skipped', 'skipped', $submission_id, $form_id, [
                'reason' => 'invalid_webhook_config',
                'target_url_valid' => ($target_url !== '' && wp_http_validate_url($target_url)),
                'secret_present' => ($secret !== ''),
            ]);
            return;
        }

        if ($submission_id <= 0 || $form_id <= 0) {
            static::logQueueAudit('webhook_submission_created_queue_skipped', 'skipped', $submission_id, $form_id, [
                'reason' => 'invalid_submission_context',
            ]);
            return;
        }

        // Prevent duplicate queueing when post-submission pipeline is re-dispatched.
        $dispatch_key = 'gf_webhook_submission_created_dispatched_' . $submission_id;
        if (get_transient($dispatch_key)) {
            static::logQueueAudit('webhook_submission_created_queue_skipped', 'skipped', $submission_id, $form_id, [
                'reason' => 'dispatch_guard_hit',
                'dispatch_key' => $dispatch_key,
            ]);
            return;
        }

        $event_id = 'evt_' . wp_generate_uuid4();
        $timestamp = time();
        $site_url = home_url('/');
        $site_host = wp_parse_url($site_url, PHP_URL_HOST);
        $plugin_version = Config::VERSION;

        $event_payload = [
            'event_id' => $event_id,
            'event_type' => 'submission.created',
            'occurred_at' => gmdate('c', $timestamp),
            'timestamp' => $timestamp,
            'source' => [
                'plugin' => 'griffinforms-form-builder',
                'version' => $plugin_version,
                'site_url' => esc_url_raw($site_url),
                'site_host' => is_string($site_host) ? sanitize_text_field($site_host) : '',
                'site_id' => get_current_blog_id(),
            ],
            'data' => [
                'submission_id' => $submission_id,
                'form_id' => $form_id,
                'status' => absint($context['submission']['status'] ?? 0),
                'created' => (string) ($context['submission']['created'] ?? ''),
            ],
        ];

        $targets = static::collectActiveDeviceTargets($settings);
        $audience = [
            'scope' => 'submission.form_access',
            'selection_version' => 1,
            'selected_at' => gmdate('c'),
            'target_count' => count($targets),
        ];

        $job_id = JobRepository::getInstance()->enqueue(
            'webhook_submission_created',
            'api_webhook',
            $submission_id,
            $form_id,
            [
                'target_url' => $target_url,
                'secret' => $secret,
                'timeout' => $timeout,
                'event' => $event_payload,
                'audience' => $audience,
                'targets' => $targets,
                'attempt' => [
                    'fanout_attempt' => 0,
                    'max_fanout_attempts' => 5,
                    'backoff_seconds' => 60,
                ],
            ],
            9,
            0
        );

        if ($job_id <= 0) {
            static::logQueueAudit('webhook_submission_created_queue_failed', 'failed', $submission_id, $form_id, [
                'reason' => 'enqueue_insert_failed',
                'target_url' => $target_url,
                'event_id' => $event_id,
                'target_count' => count($targets),
            ]);
            return;
        }

        set_transient($dispatch_key, 1, DAY_IN_SECONDS);
        static::logQueueAudit('webhook_submission_created_queued', 'success', $submission_id, $form_id, [
            'job_id' => $job_id,
            'target_url' => $target_url,
            'event_id' => $event_id,
            'target_count' => count($targets),
        ]);
    }

    private static function logQueueAudit(string $action, string $outcome, int $submission_id, int $form_id, array $meta = []): void
    {
        $base_meta = [
            'channel' => 'api_webhook',
            'request_method' => 'JOB',
            'request_uri' => 'job://webhook_submission_created',
            'submission_id' => $submission_id,
            'form_id' => $form_id,
        ];

        Capabilities::logAudit($action, $outcome, [
            'target_type' => 'submission',
            'target_id' => $submission_id,
            'meta' => array_merge($base_meta, $meta),
        ]);
    }

    private static function collectActiveDeviceTargets(Settings $settings): array
    {
        global $wpdb;

        $table = Config::getInstance()->getTable('options');
        if (!$table) {
            return [];
        }

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT option_name FROM %i WHERE option_name LIKE %s ORDER BY option_name ASC",
                $table,
                self::DEVICE_OPTION_PREFIX . '%'
            ),
            ARRAY_A
        );
        if (!is_array($rows) || empty($rows)) {
            return [];
        }

        $targets = [];
        foreach ($rows as $row) {
            $option_name = sanitize_text_field((string) ($row['option_name'] ?? ''));
            if ($option_name === '' || strpos($option_name, self::DEVICE_OPTION_PREFIX) !== 0) {
                continue;
            }

            $user_id = absint(substr($option_name, strlen(self::DEVICE_OPTION_PREFIX)));
            if ($user_id <= 0) {
                continue;
            }

            $registry = $settings->getOption($option_name, []);
            if (!is_array($registry) || empty($registry)) {
                continue;
            }

            foreach ($registry as $record) {
                if (!is_array($record)) {
                    continue;
                }

                $device_id = sanitize_text_field((string) ($record['device_id'] ?? ''));
                if ($device_id === '' || !preg_match('/^[A-Za-z0-9._:-]+$/', $device_id)) {
                    continue;
                }

                $token_state = sanitize_key((string) ($record['token_state'] ?? 'active'));
                $deleted_at = sanitize_text_field((string) ($record['deleted_at'] ?? ''));
                $push_token = sanitize_text_field((string) ($record['push_token'] ?? ''));
                if ($token_state !== 'active' || $deleted_at !== '' || $push_token === '') {
                    continue;
                }

                $labels = [];
                $raw_labels = $record['labels'] ?? [];
                if (is_array($raw_labels)) {
                    foreach ($raw_labels as $label) {
                        $clean = sanitize_text_field((string) $label);
                        if ($clean !== '') {
                            $labels[] = $clean;
                        }
                    }
                    $labels = array_values(array_unique($labels));
                }

                $targets[] = [
                    'user_id' => $user_id,
                    'device_id' => $device_id,
                    'platform' => sanitize_key((string) ($record['platform'] ?? 'unknown')),
                    'push_token' => $push_token,
                    'token_state' => $token_state,
                    'labels' => $labels,
                    'last_seen' => sanitize_text_field((string) ($record['last_seen'] ?? '')),
                ];
            }
        }

        return $targets;
    }
}
