<?php

namespace WPSN\Modules\Licensing;

defined('ABSPATH') || exit;

/**
 * Central Free / Pro / Agency matrix used by admin UI and module gating.
 *
 * The self-serve backend can still expose marketing labels such as “Starter”,
 * but inside the plugin we normalize everything to these three runtime tiers.
 */
final class PlanMatrix
{
    public const TIER_FREE = 'free';
    public const TIER_PRO = 'pro';
    public const TIER_AGENCY = 'agency';

    /**
     * @return array<string,array<string,mixed>>
     */
    public static function tiers(): array
    {
        $tiers = [
            self::TIER_FREE => [
                'label' => __('Free', 'wp-seo-next'),
                'description' => __('Core SEO stack for metadata, schema, sitemap, robots, redirects, search URL cleanup, Woo/local SEO, imports, and editor tooling.', 'wp-seo-next'),
                'features' => [
                    __('Meta tags, social cards, schema, sitemap, robots.txt, breadcrumbs, and redirects.', 'wp-seo-next'),
                    __('WooCommerce, Local SEO, Author SEO, archive controls, and search URL cleanup.', 'wp-seo-next'),
                    __('Import/export, editor helpers, media metadata, IndexNow, and front-end inspection.', 'wp-seo-next'),
                ],
            ],
            self::TIER_PRO => [
                'label' => __('Pro', 'wp-seo-next'),
                'description' => __('Growth toolkit for research, crawl operations, content planning, and advanced automation.', 'wp-seo-next'),
                'features' => [
                    __('Search Console insights and internal search research.', 'wp-seo-next'),
                    __('SERP Gap, Content Clusters, Crawl Budget, Site Crawl, Issues, and Tasks Hub.', 'wp-seo-next'),
                    __('Automation flows for repeatable SEO operations.', 'wp-seo-next'),
                ],
            ],
            self::TIER_AGENCY => [
                'label' => __('Agency', 'wp-seo-next'),
                'description' => __('Client-hand-off and fleet operations for agencies, consultants, and managed SEO teams.', 'wp-seo-next'),
                'features' => [
                    __('Config Sync presets, Multisite tooling, and white-label friendly client hand-off.', 'wp-seo-next'),
                    __('Auto-Pilot, Drift Monitor, and SaaS policy sync for managed environments.', 'wp-seo-next'),
                    __('SaaS dashboard connectivity and agency-wide operational controls.', 'wp-seo-next'),
                ],
            ],
        ];

        $filtered = apply_filters('wpsn/licensing/plan_tiers', $tiers);
        return is_array($filtered) ? $filtered : $tiers;
    }

    /**
     * @return array<string,array<string,mixed>>
     */
    public static function moduleRules(): array
    {
        $rules = [
            'search_console' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['gsc', 'search_console', 'plan_pro', 'plan_agency'],
            ],
            'serp_gap' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['serp_gap', 'plan_pro', 'plan_agency'],
            ],
            'content_clusters' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['content_clusters', 'clusters', 'plan_pro', 'plan_agency'],
            ],
            'crawl_budget' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['crawl_budget', 'crawl', 'plan_pro', 'plan_agency'],
            ],
            'site_crawl' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['site_crawl', 'crawl', 'plan_pro', 'plan_agency'],
            ],
            'issues' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['issues', 'crawl', 'plan_pro', 'plan_agency'],
            ],
            'tasks_hub' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['tasks_hub', 'crawl', 'plan_pro', 'plan_agency'],
            ],
            'automation' => [
                'tier' => self::TIER_PRO,
                'entitlements' => ['automation', 'plan_pro', 'plan_agency'],
            ],
            'multisite' => [
                'tier' => self::TIER_AGENCY,
                'entitlements' => ['multisite', 'agency_ops', 'plan_agency'],
            ],
            'config_sync' => [
                'tier' => self::TIER_AGENCY,
                'entitlements' => ['config_sync', 'agency_ops', 'plan_agency'],
            ],
            'autopilot' => [
                'tier' => self::TIER_AGENCY,
                'entitlements' => ['autopilot', 'agency_ops', 'plan_agency'],
            ],
            'drift_monitor' => [
                'tier' => self::TIER_AGENCY,
                'entitlements' => ['drift_monitor', 'agency_ops', 'plan_agency'],
            ],
            'saas_sync' => [
                'tier' => self::TIER_AGENCY,
                'entitlements' => ['saas_sync', 'agency_ops', 'plan_agency'],
            ],
            'saas_dashboard' => [
                'tier' => self::TIER_AGENCY,
                'entitlements' => ['saas_dashboard', 'agency_ops', 'plan_agency'],
            ],
        ];

        $filtered = apply_filters('wpsn/licensing/module_plan_rules', $rules);
        return is_array($filtered) ? $filtered : $rules;
    }

    public static function buildTier(): string
    {
        return self::normalizePlan(defined('WPSN_BUILD') ? (string) WPSN_BUILD : self::TIER_FREE);
    }

    public static function tierLabel(string $tier): string
    {
        $tier = self::normalizePlan($tier);
        $tiers = self::tiers();

        if (isset($tiers[$tier]['label']) && is_string($tiers[$tier]['label']) && $tiers[$tier]['label'] !== '') {
            return $tiers[$tier]['label'];
        }

        return ucfirst($tier);
    }

    public static function normalizePlan(string $plan): string
    {
        $plan = sanitize_key($plan);
        if ($plan === '') {
            return self::TIER_FREE;
        }

        $aliases = [
            'free' => self::TIER_FREE,
            'starter' => self::TIER_FREE,
            'basic' => self::TIER_FREE,
            'core' => self::TIER_FREE,
            'community' => self::TIER_FREE,
            'oss' => self::TIER_FREE,
            'free_local' => self::TIER_FREE,
            'free_unlimited' => self::TIER_FREE,
            'pro' => self::TIER_PRO,
            'professional' => self::TIER_PRO,
            'plus' => self::TIER_PRO,
            'business' => self::TIER_PRO,
            'growth' => self::TIER_PRO,
            'pro1' => self::TIER_PRO,
            'pro5' => self::TIER_PRO,
            'pro10' => self::TIER_PRO,
            'pro_1' => self::TIER_PRO,
            'pro_5' => self::TIER_PRO,
            'pro_10' => self::TIER_PRO,
            'agency' => self::TIER_AGENCY,
            'team' => self::TIER_AGENCY,
            'enterprise' => self::TIER_AGENCY,
            'white_label' => self::TIER_AGENCY,
            'agency25' => self::TIER_AGENCY,
            'agency100' => self::TIER_AGENCY,
            'agencyunlimited' => self::TIER_AGENCY,
            'agency_25' => self::TIER_AGENCY,
            'agency_100' => self::TIER_AGENCY,
            'agency_unlimited' => self::TIER_AGENCY,
        ];

        return $aliases[$plan] ?? self::TIER_FREE;
    }

    public static function tierRank(string $tier): int
    {
        $tier = self::normalizePlan($tier);
        switch ($tier) {
            case self::TIER_AGENCY:
                return 3;
            case self::TIER_PRO:
                return 2;
            case self::TIER_FREE:
            default:
                return 1;
        }
    }

    public static function buildAllowsTier(string $requiredTier, ?string $buildTier = null): bool
    {
        $requiredTier = self::normalizePlan($requiredTier);
        $buildTier = self::normalizePlan($buildTier ?? self::buildTier());

        return self::isAtLeast($buildTier, $requiredTier);
    }

    public static function buildAllowsModule(string $moduleId, ?string $buildTier = null): bool
    {
        $moduleId = sanitize_key($moduleId);
        if ($moduleId === '') {
            return false;
        }

        return self::buildAllowsTier(self::moduleTier($moduleId), $buildTier);
    }

    public static function isPremiumModule(string $moduleId): bool
    {
        return self::moduleTier($moduleId) !== self::TIER_FREE;
    }

    public static function isAtLeast(string $plan, string $required): bool
    {
        return self::tierRank($plan) >= self::tierRank($required);
    }

    public static function moduleTier(string $moduleId): string
    {
        $moduleId = sanitize_key($moduleId);
        $rules = self::moduleRules();
        if (!isset($rules[$moduleId]) || !is_array($rules[$moduleId])) {
            return self::TIER_FREE;
        }

        $tier = isset($rules[$moduleId]['tier']) ? (string) $rules[$moduleId]['tier'] : self::TIER_FREE;
        return self::normalizePlan($tier);
    }

    /**
     * @return string[]
     */
    public static function moduleEntitlements(string $moduleId): array
    {
        $moduleId = sanitize_key($moduleId);
        $rules = self::moduleRules();
        if (!isset($rules[$moduleId]) || !is_array($rules[$moduleId])) {
            return [];
        }

        $entitlements = isset($rules[$moduleId]['entitlements']) && is_array($rules[$moduleId]['entitlements']) ? $rules[$moduleId]['entitlements'] : [];
        return self::normalizeEntitlements($entitlements);
    }

    /**
     * @return array{required_tier:string,allowed:bool,entitlements:string[]}
     */
    public static function moduleAccess(string $moduleId, string $currentPlan, array $entitlements = [], ?string $buildTier = null): array
    {
        $requiredTier = self::moduleTier($moduleId);
        $moduleEntitlements = self::moduleEntitlements($moduleId);
        $normalizedPlan = self::normalizePlan($currentPlan);
        $normalizedEntitlements = self::normalizeEntitlements($entitlements);
        $effectiveBuildTier = self::normalizePlan($buildTier ?? self::buildTier());
        $includedInBuild = self::buildAllowsModule($moduleId, $effectiveBuildTier);

        $allowed = $includedInBuild && (
            ($requiredTier === self::TIER_FREE)
            || self::isAtLeast($normalizedPlan, $requiredTier)
            || !empty(array_intersect($moduleEntitlements, $normalizedEntitlements))
        );

        return [
            'required_tier' => $requiredTier,
            'allowed' => $allowed,
            'entitlements' => $moduleEntitlements,
            'build_tier' => $effectiveBuildTier,
            'included_in_build' => $includedInBuild,
        ];
    }

    /**
     * @param array<string,mixed>|null $licenseCache
     * @param array<string,mixed>|null $billingCache
     */
    public static function detectCurrentPlan(?array $licenseCache = null, ?array $billingCache = null, array $entitlements = []): string
    {
        $candidates = [
            $billingCache['billing']['plan'] ?? '',
            $licenseCache['license']['plan'] ?? '',
            $licenseCache['license']['billing']['plan'] ?? '',
            $billingCache['plan'] ?? '',
            $licenseCache['plan'] ?? '',
        ];

        foreach ($candidates as $candidate) {
            if (!is_scalar($candidate)) {
                continue;
            }
            $candidate = trim((string) $candidate);
            if ($candidate === '') {
                continue;
            }

            return self::normalizePlan($candidate);
        }

        if (empty($entitlements) && $licenseCache && isset($licenseCache['license']['entitlements']) && is_array($licenseCache['license']['entitlements'])) {
            $entitlements = $licenseCache['license']['entitlements'];
        }

        return self::inferPlanFromEntitlements($entitlements);
    }

    /**
     * @return string[]
     */
    public static function modulesForTier(string $tier): array
    {
        $tier = self::normalizePlan($tier);
        $out = [];

        foreach (self::moduleRules() as $moduleId => $rule) {
            if (!is_array($rule)) {
                continue;
            }
            if (self::normalizePlan((string) ($rule['tier'] ?? '')) !== $tier) {
                continue;
            }
            $out[] = sanitize_key((string) $moduleId);
        }

        return array_values(array_filter($out, static fn ($id) => is_string($id) && $id !== ''));
    }

    public static function inferPlanFromEntitlements(array $entitlements): string
    {
        $entitlements = self::normalizeEntitlements($entitlements);

        $agencyHints = [
            'plan_agency',
            'agency',
            'agency_ops',
            'autopilot',
            'config_sync',
            'drift_monitor',
            'multisite',
            'saas_sync',
            'saas_dashboard',
        ];
        if (!empty(array_intersect($agencyHints, $entitlements))) {
            return self::TIER_AGENCY;
        }

        $proHints = [
            'plan_pro',
            'pro',
            'automation',
            'gsc',
            'search_console',
            'serp_gap',
            'content_clusters',
            'clusters',
            'crawl_budget',
            'site_crawl',
            'issues',
            'tasks_hub',
            'crawl',
        ];
        if (!empty(array_intersect($proHints, $entitlements))) {
            return self::TIER_PRO;
        }

        return self::TIER_FREE;
    }

    /**
     * @param array<int,mixed> $entitlements
     * @return string[]
     */
    private static function normalizeEntitlements(array $entitlements): array
    {
        $out = [];
        foreach ($entitlements as $entitlement) {
            if (!is_scalar($entitlement)) {
                continue;
            }
            $key = sanitize_key((string) $entitlement);
            if ($key === '') {
                continue;
            }
            $out[$key] = $key;
        }

        return array_values($out);
    }
}
