<?php
namespace EncypherProvenance;

if (! defined('ABSPATH')) {
    exit;
}

/**
 * Server-owned WordPress capability projection.
 *
 * Commercial policy never derives from a local tier string or a submitted
 * WordPress setting. The Encypher /account response resolves plans, admin
 * overrides, and add-ons into this versioned capability map. WordPress caches
 * that projection only long enough to keep ordinary requests local; an absent,
 * malformed, mismatched, or stale projection denies every paid capability.
 */
final class Capabilities
{
    public const OPTION_KEY = 'encypher_provenance_server_entitlements';
    public const SCHEMA_VERSION = 1;
    public const SOURCE = 'encypher';
    public const MAX_AGE = 86400;
    public const REFRESH_AFTER = 21600;

    public const WHITE_LABEL = 'white_label';
    public const MANIFEST_VIEWER = 'manifest_viewer';
    public const MULTISITE = 'multisite';
    public const BULK_OPERATIONS = 'bulk_operations';
    public const LOCAL_HASH_SIGNING = 'local_hash_signing';
    public const SEGMENT_RIGHTS = 'segment_rights';
    public const BYOK = 'byok';
    public const ADVANCED_ANALYTICS = 'advanced_analytics';
    public const ADVANCED_SIGNING = 'advanced_signing';
    public const TRUSTMARK_WATERMARK = 'trustmark_watermark';
    public const AUDIO_WATERMARK = 'audio_watermark';
    public const ERROR_WEBHOOK = 'error_webhook';
    public const VIDEO_WATERMARK = 'video_watermark';

    /** @var list<string> */
    private const KNOWN = [
        self::WHITE_LABEL,
        self::MANIFEST_VIEWER,
        self::MULTISITE,
        self::BULK_OPERATIONS,
        self::LOCAL_HASH_SIGNING,
        self::SEGMENT_RIGHTS,
        self::BYOK,
        self::ADVANCED_ANALYTICS,
        self::ADVANCED_SIGNING,
        self::TRUSTMARK_WATERMARK,
        self::ERROR_WEBHOOK,
        self::AUDIO_WATERMARK,
        self::VIDEO_WATERMARK,
    ];

    public static function allows(string $capability): bool
    {
        return self::snapshot_allows(self::snapshot(), $capability);
    }

    /** @param array<string,mixed> $snapshot */
    public static function snapshot_allows(array $snapshot, string $capability): bool
    {
        return in_array($capability, self::KNOWN, true)
            && self::is_current_shape($snapshot)
            && true === ($snapshot['capabilities'][$capability] ?? false);
    }

    /** @return array<string,mixed> */
    public static function snapshot(): array
    {
        if (! function_exists('get_option')) {
            return [];
        }
        $raw = get_option(self::OPTION_KEY, []);
        if (! is_array($raw) || ! self::is_current($raw)) {
            return [];
        }
        return $raw;
    }

    /** @param array<string,mixed> $raw */
    public static function store_server_projection(array $raw, string $organization_id): bool
    {
        $normalized = self::normalize_server_projection($raw, $organization_id);
        if (empty($normalized)) {
            self::clear();
            return false;
        }
        if (! function_exists('update_option')) {
            return false;
        }
        return false !== update_option(self::OPTION_KEY, $normalized, false);
    }

    public static function clear(): void
    {
        if (function_exists('delete_option')) {
            delete_option(self::OPTION_KEY);
        }
    }
    public static function needs_refresh(): bool
    {
        if (! function_exists('get_option')) {
            return true;
        }
        $raw = get_option(self::OPTION_KEY, []);
        if (! is_array($raw) || ! self::is_current($raw)) {
            return true;
        }
        return (int) ($raw['checked_at'] ?? 0) < time() - self::REFRESH_AFTER;
    }

    /** @param array<string,mixed> $raw */
    private static function normalize_server_projection(array $raw, string $organization_id): array
    {
        if (
            self::SCHEMA_VERSION !== (int) ($raw['schema_version'] ?? 0)
            || self::SOURCE !== (string) ($raw['source'] ?? '')
            || ! isset($raw['capabilities'])
            || ! is_array($raw['capabilities'])
            || '' === trim($organization_id)
        ) {
            return [];
        }

        $capabilities = array_fill_keys(self::KNOWN, false);
        foreach (self::KNOWN as $capability) {
            $capabilities[$capability] = true === ($raw['capabilities'][$capability] ?? false);
        }

        return [
            'schema_version' => self::SCHEMA_VERSION,
            'source' => self::SOURCE,
            'organization_id' => sanitize_text_field($organization_id),
            'checked_at' => time(),
            'capabilities' => $capabilities,
        ];
    }

    /** @param array<string,mixed> $snapshot */
    private static function is_current(array $snapshot): bool
    {
        return self::matches_current_account($snapshot)
            && self::is_current_shape($snapshot);
    }

    /** @param array<string,mixed> $snapshot */
    private static function is_current_shape(array $snapshot): bool
    {
        $checked_at = (int) ($snapshot['checked_at'] ?? 0);
        return self::SCHEMA_VERSION === (int) ($snapshot['schema_version'] ?? 0)
            && self::SOURCE === (string) ($snapshot['source'] ?? '')
            && $checked_at >= time() - self::MAX_AGE
            && $checked_at <= time() + 300
            && '' !== trim((string) ($snapshot['organization_id'] ?? ''))
            && isset($snapshot['capabilities'])
            && is_array($snapshot['capabilities']);
    }

    /** @param array<string,mixed> $snapshot */
    private static function matches_current_account(array $snapshot): bool
    {
        if (
            self::SCHEMA_VERSION !== (int) ($snapshot['schema_version'] ?? 0)
            || self::SOURCE !== (string) ($snapshot['source'] ?? '')
        ) {
            return false;
        }
        if (! function_exists('get_option')) {
            return false;
        }
        $settings = get_option('encypher_provenance_settings', []);
        $current_org = is_array($settings) ? trim((string) ($settings['organization_id'] ?? '')) : '';
        return '' !== $current_org
            && hash_equals($current_org, trim((string) ($snapshot['organization_id'] ?? '')));
    }
}
