<?php

namespace FleekDash\Realtime;

defined('ABSPATH') || exit;

class RealtimeEventOutboxService
{
    private const TABLE = 'fleekdash_realtime_event_outbox';
    private const CRON_HOOK = 'fleekdash_realtime_dispatch_outbox';

    public static function init(): void
    {
        \add_filter('cron_schedules', function (array $schedules): array {
            if (!isset($schedules['minute'])) {
                $schedules['minute'] = [
                    'interval' => 60,
                    'display' => 'Every Minute',
                ];
            }
            return $schedules;
        });
        \add_action(self::CRON_HOOK, [self::class, 'dispatchPendingEvents']);
        if (!\wp_next_scheduled(self::CRON_HOOK)) {
            \wp_schedule_event(time() + 60, 'minute', self::CRON_HOOK);
        }
    }

    public static function enqueue(string $eventType, array $payload, array $meta = []): bool
    {
        if (!self::outboxTableExists()) {
            return false;
        }

        $result = false;
        $isAvailable = RealtimeConfig::isEnabled() && !RealtimeConfig::isKillSwitchEnabled();
        $encodedPayload = \wp_json_encode($payload);
        $isPayloadValid = is_string($encodedPayload)
            && strlen($encodedPayload) <= RealtimeConfig::getMaxPayloadBytes();
        $canPublish = self::canPublishNow();

        if ($isAvailable && $isPayloadValid && $canPublish) {
            global $wpdb;
            $table = $wpdb->prefix . self::TABLE;
            $eventId = self::generateEventId();
            $tenantId = isset($meta['tenant_id']) ? (string) $meta['tenant_id'] : RealtimeConfig::getTenantId();
            $userId = isset($meta['user_id']) ? (int) $meta['user_id'] : null;
            $scope = isset($meta['scope']) ? (string) $meta['scope'] : 'all';
            $now = \current_time('mysql');

            $insertResult = $wpdb->insert($table, [
                'event_id' => $eventId,
                'event_type' => $eventType,
                'tenant_id' => $tenantId,
                'user_id' => $userId ?: null,
                'scope' => $scope,
                'payload_min' => $encodedPayload,
                'status' => 'pending',
                'attempts' => 0,
                'available_at' => $now,
                'created_at' => $now,
                'updated_at' => $now,
            ]);
            $result = $insertResult !== false;
        }

        return $result;
    }

    public static function emitPermissionsUpdated(int $userId, array $affectedPermissions = [], string $scope = 'user'): bool
    {
        return self::enqueue('permissions.updated', [
            'affected_permissions' => $affectedPermissions,
        ], [
            'user_id' => $userId,
            'scope' => $scope,
        ]);
    }

    public static function emitFeaturesUpdated(int $userId, array $features = [], array $limits = []): bool
    {
        return self::enqueue('features.updated', [
            'features' => $features,
            'limits' => $limits,
        ], [
            'user_id' => $userId,
            'scope' => 'user',
        ]);
    }

    public static function emitNotificationCreated(int $userId, string $message, array $meta = []): bool
    {
        return self::enqueue('notifications.created', [
            'message' => $message,
            'meta' => $meta,
        ], [
            'user_id' => $userId,
            'scope' => 'user',
        ]);
    }

    public static function dispatchPendingEvents(): void
    {
        if (!RealtimeConfig::isEnabled() || RealtimeConfig::isKillSwitchEnabled()) {
            return;
        }
        if (!self::outboxTableExists()) {
            return;
        }
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE;
        $now = \current_time('mysql');
        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, event_id, event_type, tenant_id, user_id, scope, payload_min, attempts
                 FROM {$table}
                 WHERE status = %s AND available_at <= %s
                 ORDER BY id ASC
                 LIMIT 50",
                'pending',
                $now
            ),
            ARRAY_A
        );

        if (!is_array($rows) || empty($rows)) {
            return;
        }

        foreach ($rows as $row) {
            $ok = self::publishRow($row);
            if ($ok) {
                self::markDispatched((int) $row['id']);
            } else {
                self::markFailed((int) $row['id'], (int) $row['attempts']);
            }
        }
    }

    public static function getStats(): array
    {
        $totals = [
            'pending' => 0,
            'dispatched' => 0,
            'failed' => 0,
        ];
        if (self::outboxTableExists()) {
            global $wpdb;
            $table = $wpdb->prefix . self::TABLE;
            $rows = $wpdb->get_results(
                "SELECT status, COUNT(*) as total FROM {$table} GROUP BY status",
                ARRAY_A
            );
            if (is_array($rows)) {
                foreach ($rows as $row) {
                    $status = (string) ($row['status'] ?? '');
                    if (isset($totals[$status])) {
                        $totals[$status] = (int) ($row['total'] ?? 0);
                    }
                }
            }
        }
        return [
            'queue' => $totals,
            'enabled' => RealtimeConfig::isEnabled(),
            'provider' => RealtimeConfig::getProvider(),
            'ws_url' => RealtimeConfig::getWsUrl(),
            'relay_endpoint' => RealtimeConfig::getRelayEndpoint(),
            'ws_configured' => RealtimeConfig::isClientTransportConfigured(),
            'relay_configured' => RealtimeConfig::isRelayConfigured(),
            'supabase_broadcast_configured' => \FleekDash\Realtime\SupabaseRealtimePublisher::isConfigured(),
            'rate_limit_per_minute' => RealtimeConfig::getRateLimitPerMinute(),
            'max_payload_bytes' => RealtimeConfig::getMaxPayloadBytes(),
            'max_subscriptions_per_client' => RealtimeConfig::getMaxSubscriptionsPerClient(),
            'killswitch' => RealtimeConfig::isKillSwitchEnabled(),
            'rollout_cohort' => RealtimeConfig::getRolloutCohort(),
        ];
    }

    private static function publishRow(array $row): bool
    {
        $envelope = self::buildEnvelopeFromRow($row);
        $published = false;
        $useSupabase = RealtimeConfig::getProvider() === 'managed'
            && \FleekDash\Realtime\SupabaseRealtimePublisher::isConfigured();

        if ($useSupabase) {
            $published = \FleekDash\Realtime\SupabaseRealtimePublisher::broadcast(
                (string) $row['tenant_id'],
                $envelope,
            );
        }

        if (!$published) {
            $published = self::publishEnvelopeViaRelay($envelope);
        }

        return $published;
    }

    /**
     * @return array<string, mixed>
     */
    private static function buildEnvelopeFromRow(array $row): array
    {
        return [
            'event_id' => (string) $row['event_id'],
            'event_type' => (string) $row['event_type'],
            'tenant_id' => (string) $row['tenant_id'],
            'user_id' => !empty($row['user_id']) ? (int) $row['user_id'] : null,
            'scope' => (string) $row['scope'],
            'issued_at' => gmdate('c'),
            'version' => 1,
            'payload_min' => json_decode((string) $row['payload_min'], true),
        ];
    }

    /**
     * @param array<string, mixed> $envelope
     */
    private static function publishEnvelopeViaRelay(array $envelope): bool
    {
        $result = false;
        $endpoint = RealtimeConfig::getRelayEndpoint();
        $token = RealtimeConfig::getRelayToken();
        $canPost = $endpoint !== '' && $token !== '';

        if ($canPost) {
            $response = \wp_remote_post($endpoint, [
                'headers' => [
                    'Authorization' => 'Bearer ' . $token,
                    'Content-Type' => 'application/json',
                ],
                'timeout' => 10,
                'body' => \wp_json_encode($envelope),
            ]);
            if (!\is_wp_error($response)) {
                $code = (int) \wp_remote_retrieve_response_code($response);
                $result = $code >= 200 && $code < 300;
            }
        }

        return $result;
    }

    private static function markDispatched(int $id): void
    {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE;
        $wpdb->update($table, [
            'status' => 'dispatched',
            'dispatched_at' => \current_time('mysql'),
            'updated_at' => \current_time('mysql'),
        ], ['id' => $id]);
    }

    private static function markFailed(int $id, int $attempts): void
    {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE;
        $nextAttempts = $attempts + 1;
        $status = $nextAttempts >= 10 ? 'failed' : 'pending';
        $delay = min(900, 30 * $nextAttempts);
        $availableAt = gmdate('Y-m-d H:i:s', time() + $delay);
        $wpdb->update($table, [
            'status' => $status,
            'attempts' => $nextAttempts,
            'last_error' => $status === 'failed' ? 'max_attempts' : null,
            'available_at' => $availableAt,
            'updated_at' => \current_time('mysql'),
        ], ['id' => $id]);
    }

    private static function canPublishNow(): bool
    {
        $limit = RealtimeConfig::getRateLimitPerMinute();
        $bucket = 'fleekdash_realtime_publish_rate_' . gmdate('YmdHi');
        $count = (int) \get_transient($bucket);
        if ($count >= $limit) {
            return false;
        }
        \set_transient($bucket, $count + 1, 70);
        return true;
    }

    private static function generateEventId(): string
    {
        try {
            return bin2hex(random_bytes(16));
        } catch (\Exception $e) {
            return md5(uniqid('fd_rt_', true));
        }
    }

    /**
     * Outbox table is created by migration 2026_03_20_000001_create_realtime_outbox_table.
     * Skip reads/writes until migrations have run to avoid log noise and DB errors.
     */
    private static function outboxTableExists(): bool
    {
        global $wpdb;
        $table = $wpdb->prefix . self::TABLE;

        return $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table;
    }
}

