<?php

namespace GriffinForms\Includes\Security;

use GriffinForms\Log;
use GriffinForms\Settings;

/**
 * Central capability and API-access policy manager.
 */
class Capabilities
{
    public const CAP_VIEW_SUBMISSIONS = 'griffinforms_view_submissions';
    public const CAP_VIEW_SUBMISSION_DETAIL = 'griffinforms_view_submission_detail';
    public const CAP_EXPORT_SUBMISSION_PDF = 'griffinforms_export_submission_pdf';
    public const CAP_SHARE_SUBMISSION_LINK = 'griffinforms_share_submission_link';
    public const CAP_DELETE_SUBMISSION = 'griffinforms_delete_submission';
    public const CAP_BULK_DELETE_SUBMISSIONS = 'griffinforms_bulk_delete_submissions';
    public const CAP_MOVE_FORM_FOLDER = 'griffinforms_move_form_folder';
    public const CAP_CREATE_FOLDER = 'griffinforms_create_folder';
    public const CAP_RENAME_FOLDER = 'griffinforms_rename_folder';
    public const CAP_RENAME_FORM = 'griffinforms_rename_form';
    public const CAP_MANAGE_SUBMISSION_READ_STATE = 'griffinforms_manage_submission_read_state';
    public const CAP_VIEW_DEVICES = 'griffinforms_view_devices';
    public const CAP_MANAGE_DEVICES = 'griffinforms_manage_devices';

    public const OPTION_API_FEATURE_ENABLED = 'api_feature_enabled';
    public const OPTION_API_KILL_SWITCH = 'api_kill_switch';
    public const OPTION_API_ROLE_CAPABILITY_MAP = 'api_role_capability_map';
    public const OPTION_API_USER_CAPABILITY_OVERRIDES = 'api_user_capability_overrides';

    public static function registerHooks(): void
    {
        add_action('init', [static::class, 'bootstrap'], 20);
    }

    public static function bootstrap(): void
    {
        static::seedDefaultOptions();
        static::syncFromSettings();
    }

    public static function allCapabilities(): array
    {
        return [
            static::CAP_VIEW_SUBMISSIONS,
            static::CAP_VIEW_SUBMISSION_DETAIL,
            static::CAP_EXPORT_SUBMISSION_PDF,
            static::CAP_SHARE_SUBMISSION_LINK,
            static::CAP_DELETE_SUBMISSION,
            static::CAP_BULK_DELETE_SUBMISSIONS,
            static::CAP_MOVE_FORM_FOLDER,
            static::CAP_CREATE_FOLDER,
            static::CAP_RENAME_FOLDER,
            static::CAP_RENAME_FORM,
            static::CAP_MANAGE_SUBMISSION_READ_STATE,
            static::CAP_VIEW_DEVICES,
            static::CAP_MANAGE_DEVICES,
        ];
    }

    public static function roleDefaultMatrix(): array
    {
        $defaults = [
            'administrator' => [
                static::CAP_VIEW_SUBMISSIONS => true,
                static::CAP_VIEW_SUBMISSION_DETAIL => true,
                static::CAP_EXPORT_SUBMISSION_PDF => true,
                static::CAP_SHARE_SUBMISSION_LINK => true,
                static::CAP_DELETE_SUBMISSION => true,
                static::CAP_BULK_DELETE_SUBMISSIONS => true,
                static::CAP_MOVE_FORM_FOLDER => true,
                static::CAP_CREATE_FOLDER => true,
                static::CAP_RENAME_FOLDER => true,
                static::CAP_RENAME_FORM => true,
                static::CAP_MANAGE_SUBMISSION_READ_STATE => true,
                static::CAP_VIEW_DEVICES => true,
                static::CAP_MANAGE_DEVICES => true,
            ],
            'editor' => [
                static::CAP_VIEW_SUBMISSIONS => true,
                static::CAP_VIEW_SUBMISSION_DETAIL => true,
                static::CAP_EXPORT_SUBMISSION_PDF => true,
                static::CAP_SHARE_SUBMISSION_LINK => true,
                static::CAP_DELETE_SUBMISSION => false,
                static::CAP_BULK_DELETE_SUBMISSIONS => false,
                static::CAP_MOVE_FORM_FOLDER => true,
                static::CAP_CREATE_FOLDER => true,
                static::CAP_RENAME_FOLDER => true,
                static::CAP_RENAME_FORM => true,
                static::CAP_MANAGE_SUBMISSION_READ_STATE => true,
                static::CAP_VIEW_DEVICES => true,
                static::CAP_MANAGE_DEVICES => true,
            ],
            'author' => [
                static::CAP_VIEW_SUBMISSIONS => false,
                static::CAP_VIEW_SUBMISSION_DETAIL => false,
                static::CAP_EXPORT_SUBMISSION_PDF => false,
                static::CAP_SHARE_SUBMISSION_LINK => false,
                static::CAP_DELETE_SUBMISSION => false,
                static::CAP_BULK_DELETE_SUBMISSIONS => false,
                static::CAP_MOVE_FORM_FOLDER => false,
                static::CAP_CREATE_FOLDER => false,
                static::CAP_RENAME_FOLDER => false,
                static::CAP_RENAME_FORM => false,
                static::CAP_MANAGE_SUBMISSION_READ_STATE => false,
                static::CAP_VIEW_DEVICES => false,
                static::CAP_MANAGE_DEVICES => false,
            ],
            'contributor' => [
                static::CAP_VIEW_SUBMISSIONS => false,
                static::CAP_VIEW_SUBMISSION_DETAIL => false,
                static::CAP_EXPORT_SUBMISSION_PDF => false,
                static::CAP_SHARE_SUBMISSION_LINK => false,
                static::CAP_DELETE_SUBMISSION => false,
                static::CAP_BULK_DELETE_SUBMISSIONS => false,
                static::CAP_MOVE_FORM_FOLDER => false,
                static::CAP_CREATE_FOLDER => false,
                static::CAP_RENAME_FOLDER => false,
                static::CAP_RENAME_FORM => false,
                static::CAP_MANAGE_SUBMISSION_READ_STATE => false,
                static::CAP_VIEW_DEVICES => false,
                static::CAP_MANAGE_DEVICES => false,
            ],
            'subscriber' => [
                static::CAP_VIEW_SUBMISSIONS => false,
                static::CAP_VIEW_SUBMISSION_DETAIL => false,
                static::CAP_EXPORT_SUBMISSION_PDF => false,
                static::CAP_SHARE_SUBMISSION_LINK => false,
                static::CAP_DELETE_SUBMISSION => false,
                static::CAP_BULK_DELETE_SUBMISSIONS => false,
                static::CAP_MOVE_FORM_FOLDER => false,
                static::CAP_CREATE_FOLDER => false,
                static::CAP_RENAME_FOLDER => false,
                static::CAP_RENAME_FORM => false,
                static::CAP_MANAGE_SUBMISSION_READ_STATE => false,
                static::CAP_VIEW_DEVICES => false,
                static::CAP_MANAGE_DEVICES => false,
            ],
        ];

        // Migration-safe baseline for discovered custom roles:
        // roles with manage_options inherit administrator-like defaults,
        // all other unknown roles default to denied.
        if (function_exists('wp_roles')) {
            $roles = wp_roles();
            if (is_object($roles) && !empty($roles->roles)) {
                foreach ($roles->roles as $role_slug => $role_data) {
                    $role_slug = sanitize_key((string) $role_slug);
                    if ($role_slug === '' || isset($defaults[$role_slug])) {
                        continue;
                    }

                    $caps = is_array($role_data['capabilities'] ?? null) ? $role_data['capabilities'] : [];
                    $is_admin_like = !empty($caps['manage_options']);
                    $defaults[$role_slug] = $is_admin_like
                        ? [
                            static::CAP_VIEW_SUBMISSIONS => true,
                            static::CAP_VIEW_SUBMISSION_DETAIL => true,
                            static::CAP_EXPORT_SUBMISSION_PDF => true,
                            static::CAP_SHARE_SUBMISSION_LINK => true,
                            static::CAP_DELETE_SUBMISSION => true,
                            static::CAP_BULK_DELETE_SUBMISSIONS => true,
                            static::CAP_MOVE_FORM_FOLDER => true,
                            static::CAP_CREATE_FOLDER => true,
                            static::CAP_RENAME_FOLDER => true,
                            static::CAP_RENAME_FORM => true,
                            static::CAP_MANAGE_SUBMISSION_READ_STATE => true,
                            static::CAP_VIEW_DEVICES => true,
                            static::CAP_MANAGE_DEVICES => true,
                        ]
                        : [
                            static::CAP_VIEW_SUBMISSIONS => false,
                            static::CAP_VIEW_SUBMISSION_DETAIL => false,
                            static::CAP_EXPORT_SUBMISSION_PDF => false,
                            static::CAP_SHARE_SUBMISSION_LINK => false,
                            static::CAP_DELETE_SUBMISSION => false,
                            static::CAP_BULK_DELETE_SUBMISSIONS => false,
                            static::CAP_MOVE_FORM_FOLDER => false,
                            static::CAP_CREATE_FOLDER => false,
                            static::CAP_RENAME_FOLDER => false,
                            static::CAP_RENAME_FORM => false,
                            static::CAP_MANAGE_SUBMISSION_READ_STATE => false,
                            static::CAP_VIEW_DEVICES => false,
                            static::CAP_MANAGE_DEVICES => false,
                        ];
                }
            }
        }

        return $defaults;
    }

    public static function seedDefaultOptions(): void
    {
        $settings = Settings::getInstance();

        if ($settings->getOption(static::OPTION_API_FEATURE_ENABLED) === null) {
            $settings->updateOption(static::OPTION_API_FEATURE_ENABLED, 1, 'capabilitymatrix');
        }

        if ($settings->getOption(static::OPTION_API_KILL_SWITCH) === null) {
            $settings->updateOption(static::OPTION_API_KILL_SWITCH, 0, 'capabilitymatrix');
        }

        if ($settings->getOption(static::OPTION_API_ROLE_CAPABILITY_MAP) === null) {
            $settings->updateOption(static::OPTION_API_ROLE_CAPABILITY_MAP, static::roleDefaultMatrix(), 'capabilitymatrix');
        }

        if ($settings->getOption(static::OPTION_API_USER_CAPABILITY_OVERRIDES) === null) {
            $settings->updateOption(static::OPTION_API_USER_CAPABILITY_OVERRIDES, (object) [], 'capabilitymatrix');
        }
    }

    public static function isApiAvailable(): bool
    {
        $settings = Settings::getInstance();
        $enabled = (int) $settings->getOption(static::OPTION_API_FEATURE_ENABLED, 1);
        $kill = (int) $settings->getOption(static::OPTION_API_KILL_SWITCH, 0);
        return $enabled === 1 && $kill !== 1;
    }

    public static function syncFromSettings(): void
    {
        $settings = Settings::getInstance();

        $stored_matrix = $settings->getOption(static::OPTION_API_ROLE_CAPABILITY_MAP, static::roleDefaultMatrix());
        $matrix = static::normalizeRoleMatrix($stored_matrix);
        static::applyRoleMatrix($matrix);

        $stored_overrides = $settings->getOption(static::OPTION_API_USER_CAPABILITY_OVERRIDES, []);
        $overrides = static::normalizeUserOverrides($stored_overrides);
        static::applyUserOverrides($overrides);
    }

    public static function normalizeRoleMatrix($matrix): array
    {
        $defaults = static::roleDefaultMatrix();
        $caps = static::allCapabilities();

        if (!is_array($matrix)) {
            return $defaults;
        }

        $normalized = [];
        foreach ($matrix as $role => $map) {
            $role_key = sanitize_key((string) $role);
            if ($role_key === '' || !is_array($map)) {
                continue;
            }
            $normalized[$role_key] = [];
            foreach ($caps as $cap) {
                $normalized[$role_key][$cap] = !empty($map[$cap]);
            }
        }

        foreach ($defaults as $role => $map) {
            if (!isset($normalized[$role])) {
                $normalized[$role] = $map;
                continue;
            }
            foreach ($caps as $cap) {
                if (!array_key_exists($cap, $normalized[$role])) {
                    $normalized[$role][$cap] = !empty($map[$cap]);
                }
            }
        }

        // Safety baseline: administrator should always retain full API capability set.
        // This prevents accidental lockout and ensures new capabilities default to allowed.
        if (isset($normalized['administrator']) && is_array($normalized['administrator'])) {
            foreach ($caps as $cap) {
                $normalized['administrator'][$cap] = true;
            }
        }

        return $normalized;
    }

    public static function normalizeUserOverrides($overrides): array
    {
        $caps = static::allCapabilities();
        if (is_object($overrides)) {
            $overrides = (array) $overrides;
        }
        if (!is_array($overrides)) {
            return [];
        }

        $normalized = [];
        foreach ($overrides as $user_id => $map) {
            $uid = absint($user_id);
            if ($uid <= 0 || !is_array($map)) {
                continue;
            }

            foreach ($caps as $cap) {
                if (array_key_exists($cap, $map)) {
                    $normalized[$uid][$cap] = !empty($map[$cap]);
                }
            }
        }

        return $normalized;
    }

    public static function applyRoleMatrix(array $matrix): void
    {
        if (!function_exists('wp_roles')) {
            return;
        }

        $roles = wp_roles();
        if (!is_object($roles) || empty($roles->roles)) {
            return;
        }

        $caps = static::allCapabilities();
        foreach (array_keys($roles->roles) as $role_slug) {
            $role = get_role($role_slug);
            if (!is_object($role)) {
                continue;
            }

            foreach ($caps as $cap) {
                $allow = isset($matrix[$role_slug][$cap]) ? (bool) $matrix[$role_slug][$cap] : false;
                if ($allow) {
                    $role->add_cap($cap);
                } else {
                    $role->remove_cap($cap);
                }
            }
        }
    }

    public static function applyUserOverrides(array $overrides): void
    {
        $caps = static::allCapabilities();

        foreach ($overrides as $user_id => $map) {
            $user = get_user_by('id', (int) $user_id);
            if (!is_object($user)) {
                continue;
            }

            foreach ($caps as $cap) {
                if (!array_key_exists($cap, $map)) {
                    continue;
                }
                if (!empty($map[$cap])) {
                    $user->add_cap($cap, true);
                } else {
                    $user->add_cap($cap, false);
                }
            }
        }
    }

    public static function logAudit(string $action, string $outcome, array $context = []): void
    {
        try {
            $log = Log::getInstance();
            $user_id = get_current_user_id();
            if (!isset($context['timestamp_utc'])) {
                $context['timestamp_utc'] = gmdate('c');
            }
            if (!isset($context['actor_user_id'])) {
                $context['actor_user_id'] = $user_id;
            }
            if (!isset($context['actor_roles'])) {
                $user = function_exists('wp_get_current_user') ? wp_get_current_user() : null;
                $context['actor_roles'] = (is_object($user) && is_array($user->roles)) ? array_values($user->roles) : [];
            }
            $msg = wp_json_encode([
                'action' => $action,
                'outcome' => $outcome,
                'context' => $context,
            ]);
            $log_type = 'info';
            if (in_array($outcome, ['denied', 'rate_limited', 'partial'], true)) {
                $log_type = 'warning';
            } elseif ($outcome === 'failed') {
                $log_type = 'error';
            }
            $category = 'audit';
            $request_uri = (string) ($_SERVER['REQUEST_URI'] ?? '');
            $context_meta = isset($context['meta']) && is_array($context['meta']) ? $context['meta'] : [];
            $context_channel = sanitize_key((string) ($context_meta['channel'] ?? ''));
            if (
                ($request_uri !== '' && strpos($request_uri, '/wp-json/griffinforms/') !== false) ||
                in_array($context_channel, ['api', 'api_webhook'], true)
            ) {
                $category = 'api_audit';
            }
            $log->add($log_type, 'submission', 0, $user_id, (string) $msg, $category);
        } catch (\Throwable $e) {
            // Never break request flow due to audit logging failures.
        }
    }
}
