<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Config;

/**
 * Storefront-domain gate. Mirrors the Zen Cart connector's
 * "Multi-environment installs" pattern.
 *
 * Behavior:
 *
 *   - If `seekmodo_wp_storefront_host` matches the current home_url
 *     host (case-insensitive, exact match), the connector is enabled.
 *
 *   - If they don't match, every gateway-talking helper returns null
 *     (via the `numinix_seekmodo_enabled` check that fans out from
 *     here) and the storefront falls back to native WP_Query.
 *
 *   - The lock can be bypassed for blue/green deploys behind a CDN
 *     via the `seekmodo_disable_storefront_host_lock` filter
 *     returning true.
 *
 *   - The locked host is initially seeded from `home_url()` at
 *     activation but is remotely overridable via tenant.snapshot from
 *     admin.seekmodo.com so the operator can fix a mis-guess without
 *     touching the WP install.
 */
final class DomainLock
{
    public static function currentHost(): string
    {
        $host = wp_parse_url(home_url(), PHP_URL_HOST);
        if (!is_string($host) || $host === '') {
            return '';
        }

        return strtolower($host);
    }

    public static function lockedHost(): string
    {
        return Options::storefrontHost();
    }

    public static function matches(): bool
    {
        if (apply_filters('seekmodo_disable_storefront_host_lock', false)) {
            return true;
        }

        $current = self::currentHost();
        $locked  = self::lockedHost();
        if ($locked === '') {
            return true; // no lock yet -> tentatively allow.
        }

        return $current === $locked;
    }

    /**
     * Stricter write-side gate (v0.5.3+).
     *
     * Mirrors {@see self::matches()} but adds a fail-closed default for
     * the most common foot-gun: a non-prod install (`dev.*`, `staging.*`)
     * that auto-seeded its `storefront_host` lock to its OWN host at
     * activation. {@see self::matches()} happily returns true in that
     * case — the lock equals current host — but writing from that
     * install pollutes the tenant's Typesense collection with non-prod
     * URLs (which is exactly the regression PM-303 hit when dev product
     * saves repopulated POCO's index with `dev.pocomarine.com` links
     * after the prod-cutover host change).
     *
     * Used by every code path that mutates gateway-side state:
     *
     *   - {@see numinix_seekmodo_run_bulk_upsert()} (indexer batches)
     *   - {@see numinix_seekmodo_mirror_click()} (event mirroring)
     *   - {@see Numinix\SeekmodoWordPress\Cron\IndexCron::tick()} early-out
     *
     * Read paths (typeahead, search swap, click beacon enqueue) keep
     * gating on the looser {@see self::matches()} so an operator
     * smoke-testing search behavior on staging still gets results.
     *
     * Two override filters, intentionally separated so a read-side
     * bypass doesn't accidentally also unlock writes:
     *
     *   - `seekmodo_disable_storefront_host_lock` (existing) — read AND
     *      write; bypasses every check. Used by blue/green CDN setups.
     *   - `seekmodo_allow_nonprod_indexing` (v0.5.3) — write only;
     *      consenting non-prod indexer when the operator KNOWS the
     *      target tenant is dev-scoped (e.g. paired to a separate
     *      `*-dev` tenant). Defaults false.
     */
    public static function canIndex(): bool
    {
        if (apply_filters('seekmodo_disable_storefront_host_lock', false)) {
            return true;
        }
        if (apply_filters('seekmodo_allow_nonprod_indexing', false)) {
            // Operator opted in. Still require the basic host-lock
            // match so a misconfigured tenant pair can't write either.
            return self::matches();
        }
        if (!self::matches()) {
            return false;
        }
        if (self::looksLikeNonProd()) {
            // Self-referential lock on a non-prod host. Fail closed
            // unless the operator has explicitly opted in via the
            // filter above. Surfaces as a "host-lock match: write
            // disabled" badge in wp-admin → Settings → Seekmodo and
            // a `wp seekmodo health` warning row.
            return false;
        }

        return true;
    }

    /**
     * Heuristic: looks at the leftmost label of the current host and
     * returns true if it looks like a non-prod subdomain. Used by the
     * admin UI to nudge the operator toward picking the prod host on
     * first install when multiple hosts have been observed, and by
     * {@see self::canIndex()} as the trigger for the write-side
     * fail-closed default (v0.5.3).
     */
    public static function looksLikeNonProd(): bool
    {
        $host = self::currentHost();
        if ($host === '') {
            return false;
        }
        $left = strtolower(explode('.', $host)[0] ?? '');
        $blockers = ['dev', 'staging', 'stage', 'test', 'testing', 'qa', 'preview', 'beta', 'preprod', 'pre-prod', 'sandbox'];

        return in_array($left, $blockers, true);
    }
}
