<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Config;

use Numinix\SeekmodoWordPress\Gateway\Client;
use Numinix\SeekmodoWordPress\Mode\ModeFsm;

/**
 * Pulls the `tenant.snapshot` from admin.seekmodo.com on a 5-min
 * cadence. The pulled snapshot can override a small subset of options
 * so admin.seekmodo.com remains the source of truth for ops-relevant
 * flags (mode, auto_promote, storefront_host, breaker thresholds).
 *
 * Snapshot shape (defensive — we tolerate missing fields):
 *
 *   {
 *     "mode": "active",
 *     "auto_promote": true,
 *     "storefront_host": "trek.ca",
 *     "show_seekmodo_branding": false,
 *     "breaker_thresholds": { "fail_threshold": 5, "window_sec": 30, "open_sec": 60 },
 *     "schema_version": 1,
 *     "snapshot_taken_at": 1717286412
 *   }
 *
 * The pull caches the merged result in a 5-min transient
 * (`seekmodo_wp_tenant_snapshot`) so the cron-driven pull doesn't
 * have to hit the gateway every request — just the storefront-host
 * filter check + the breaker thresholds read off the cached copy.
 */
final class RemoteConfig
{
    private const TRANSIENT_TTL = 300; // 5 min.

    /**
     * @return array<string,mixed>|null
     */
    public static function pull(): ?array
    {
        $resp = Client::tenantSnapshot();
        if (!is_array($resp)) {
            // Don't churn the local config on a transient gateway error.
            // We'll retry on the next 5-min cron tick.
            return null;
        }

        // Persist last-snapshot timestamp so the next pull can ask for
        // a `since=` delta and the admin UI can show "last-pull X
        // minutes ago".
        update_option(Options::OPT_LAST_SNAPSHOT_AT, time(), false);
        set_transient(Options::TRANSIENT_TENANT_SNAPSHOT, $resp, self::TRANSIENT_TTL);

        self::applyOverrides($resp);

        return $resp;
    }

    /**
     * @return array<string,mixed>|null
     */
    public static function cached(): ?array
    {
        $v = get_transient(Options::TRANSIENT_TENANT_SNAPSHOT);

        return is_array($v) ? $v : null;
    }

    /**
     * @param array<string,mixed> $snap
     */
    private static function applyOverrides(array $snap): void
    {
        if (!empty($snap['mode']) && is_string($snap['mode']) && in_array($snap['mode'], ModeFsm::ALL_MODES, true)) {
            update_option(Options::OPT_MODE, $snap['mode']);
        }
        if (isset($snap['auto_promote'])) {
            update_option(Options::OPT_AUTO_PROMOTE, !empty($snap['auto_promote']) ? '1' : '0');
        }
        if (!empty($snap['storefront_host']) && is_string($snap['storefront_host'])) {
            update_option(Options::OPT_STOREFRONT_HOST, strtolower($snap['storefront_host']));
        }
        // `show_seekmodo_branding` is a hard boolean — always mirror the
        // snapshot value (including when it's explicitly false) so an
        // operator toggle on admin.seekmodo.com from on -> off propagates
        // on the next 5-min pull. `array_key_exists` rather than `!empty`
        // because we want the false case to flow through, not be skipped.
        if (array_key_exists('show_seekmodo_branding', $snap)) {
            update_option(
                Options::OPT_SHOW_BRANDING,
                !empty($snap['show_seekmodo_branding']) ? '1' : '0'
            );
        }
        if (!empty($snap['suggest_layout']) && is_string($snap['suggest_layout'])) {
            update_option(Options::OPT_SUGGEST_LAYOUT, strtolower(trim($snap['suggest_layout'])));
        }
        if (array_key_exists('suggest_show_branding', $snap)) {
            update_option(
                Options::OPT_SUGGEST_SHOW_BRANDING,
                !empty($snap['suggest_show_branding']) ? '1' : '0'
            );
        }
        if (!empty($snap['schema_version']) && is_numeric($snap['schema_version'])) {
            $remote = (int) $snap['schema_version'];
            if ($remote > 0) {
                update_option(Options::OPT_SCHEMA_VERSION, $remote);
            }
        }
        // v0.3.0 multi-vertical gate — see Options::OPT_ENABLED_VERTICALS
        // and Bootstrap::boot(). Accepts either an array form
        // (['commerce', 'content']) or the comma-string form
        // ('commerce,content'); normalises to a comma-joined string for
        // wp_options round-trip stability. Empty / unknown / missing
        // are left untouched so Options::enabledVerticals() falls
        // through to its all-enabled stale-snapshot default — the
        // same posture every other Sprint-12+ field uses here.
        $verticalsRaw = $snap['verticals'] ?? null;
        if (is_string($verticalsRaw)) {
            $verticalsRaw = array_map('trim', explode(',', $verticalsRaw));
        }
        if (is_array($verticalsRaw)) {
            $clean = [];
            foreach ($verticalsRaw as $label) {
                if (!is_string($label)) {
                    continue;
                }
                $label = strtolower(trim($label));
                if ($label === 'commerce' || $label === 'content') {
                    $clean[$label] = true;
                }
            }
            if ($clean !== []) {
                update_option(Options::OPT_ENABLED_VERTICALS, implode(',', array_keys($clean)));
            }
        }
        // breaker_thresholds is read through the `seekmodo_breaker_thresholds`
        // filter at runtime, not persisted to wp_options. We stash the
        // remote thresholds on a separate option that the filter
        // consults via Bootstrap.
        if (!empty($snap['breaker_thresholds']) && is_array($snap['breaker_thresholds'])) {
            update_option('seekmodo_wp_remote_breaker', $snap['breaker_thresholds'], false);
        }
        if (array_key_exists('fallback_bundle', $snap)) {
            $bundle = $snap['fallback_bundle'];
            if (is_array($bundle)) {
                update_option(Options::OPT_FALLBACK_BUNDLE, $bundle, false);
            } elseif ($bundle === null) {
                delete_option(Options::OPT_FALLBACK_BUNDLE);
            }
        }
        if (array_key_exists('ask_ai_enabled', $snap)) {
            update_option(
                Options::OPT_ASK_AI_ENABLED,
                !empty($snap['ask_ai_enabled']) ? '1' : '0'
            );
        }
        if (array_key_exists('recommendations_enabled', $snap)) {
            update_option(
                Options::OPT_RECOMMENDATIONS_ENABLED,
                !empty($snap['recommendations_enabled']) ? '1' : '0'
            );
        }
    }
}
