<?php

declare(strict_types=1);

namespace ShieldX\Core;

defined('ABSPATH') || exit;

use ShieldX\Scanner\ComponentProvenance;
use ShieldX\Security\LoginSecurity;

final class SecurityPosture
{
    /** @var LoginSecurity */
    private $loginSecurity;

    /** @var ComponentProvenance */
    private $provenance;

    public function __construct(?LoginSecurity $loginSecurity = null, ?ComponentProvenance $provenance = null)
    {
        $this->loginSecurity = $loginSecurity ?: LoginSecurity::fromWordPress();
        $this->provenance = $provenance ?: ComponentProvenance::fromWordPress();
    }

    public static function fromWordPress(): self
    {
        return new self();
    }

    /**
     * @return array<int,array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}>
     */
    public function report(): array
    {
        return [
            $this->debugFinding(),
            $this->displayErrorsFinding(),
            $this->httpsFinding(),
            $this->defaultAdministratorFinding(),
            $this->stalePrivilegedFinding(),
            $this->inactiveComponentsFinding(),
            $this->excessiveSessionsFinding(),
            $this->missingTwoFactorFinding(),
            $this->provenanceFinding(),
            $this->databasePrefixFinding(),
        ];
    }

    /**
     * @return array{total:int,attention:int,healthy:int,informational:int,unavailable:int,severity:array<string,int>,provenance:array<string,mixed>}
     */
    public function summary(): array
    {
        $summary = [
            'total' => 0,
            'attention' => 0,
            'healthy' => 0,
            'informational' => 0,
            'unavailable' => 0,
            'severity' => ['critical' => 0, 'high' => 0, 'medium' => 0, 'low' => 0, 'info' => 0],
            'provenance' => $this->provenance->summary(),
        ];

        foreach ($this->report() as $finding) {
            $summary['total']++;
            $status = $finding['status'];
            if ($status === 'warning') {
                $summary['attention']++;
                $severity = isset($summary['severity'][$finding['severity']]) ? $finding['severity'] : 'info';
                $summary['severity'][$severity]++;
            } elseif ($status === 'healthy') {
                $summary['healthy']++;
            } elseif ($status === 'unavailable') {
                $summary['unavailable']++;
            } else {
                $summary['informational']++;
            }
        }

        return $summary;
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function debugFinding(): array
    {
        $enabled = defined('WP_DEBUG') && WP_DEBUG;

        return $this->finding(
            'production_debug',
            'high',
            'WordPress debug mode',
            $enabled ? 'WP_DEBUG is enabled.' : 'WP_DEBUG is not enabled.',
            $this->systemUrl(),
            $enabled ? 'warning' : 'healthy'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function displayErrorsFinding(): array
    {
        $display = strtolower((string) ini_get('display_errors'));
        $enabled = in_array($display, ['1', 'on', 'yes', 'true'], true)
            || (defined('WP_DEBUG_DISPLAY') && WP_DEBUG_DISPLAY);

        return $this->finding(
            'php_error_display',
            'high',
            'PHP error display',
            $enabled ? 'PHP errors may be displayed to visitors.' : 'PHP error display is disabled.',
            $this->systemUrl(),
            $enabled ? 'warning' : 'healthy'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function httpsFinding(): array
    {
        $home = function_exists('home_url') ? (string) home_url('/') : '';
        $site = function_exists('site_url') ? (string) site_url('/') : '';
        if ($home === '' || $site === '') {
            return $this->finding('https_urls', 'high', 'HTTPS site URLs', 'WordPress site URL evidence is unavailable.', $this->generalUrl(), 'unavailable');
        }
        $secure = stripos($home, 'https://') === 0 && stripos($site, 'https://') === 0;

        return $this->finding(
            'https_urls',
            'high',
            'HTTPS site URLs',
            $secure ? 'Home and WordPress URLs use HTTPS.' : 'Home or WordPress URL does not use HTTPS.',
            $this->generalUrl(),
            $secure ? 'healthy' : 'warning'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function defaultAdministratorFinding(): array
    {
        if (!function_exists('get_users')) {
            return $this->finding('default_admin_username', 'medium', 'Default administrator usernames', 'User evidence is unavailable.', $this->usersUrl(), 'unavailable');
        }
        $matches = [];
        foreach ((array) get_users(['role' => 'administrator', 'fields' => ['ID', 'user_login']]) as $user) {
            $login = is_object($user) ? strtolower((string) ($user->user_login ?? '')) : '';
            if (in_array($login, ['admin', 'administrator'], true)) {
                $matches[] = $login;
            }
        }

        return $this->finding(
            'default_admin_username',
            'medium',
            'Default administrator usernames',
            $matches === [] ? 'No default administrator usernames were found.' : count($matches) . ' default administrator username(s) are active.',
            $this->usersUrl(),
            $matches === [] ? 'healthy' : 'warning'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function stalePrivilegedFinding(): array
    {
        if (!function_exists('get_users') || !function_exists('get_user_meta')) {
            return $this->finding('stale_privileged_accounts', 'medium', 'Stale privileged accounts', 'Login evidence is unavailable.', $this->usersUrl(), 'unavailable');
        }

        $stale = 0;
        $unknown = 0;
        $cutoff = time() - (90 * 86400);
        foreach ((array) get_users(['fields' => 'all']) as $user) {
            if (!is_object($user) || !$this->isPrivilegedUser($user)) {
                continue;
            }
            $lastLogin = (string) get_user_meta((int) $user->ID, LoginSecurity::META_LAST_LOGIN_AT, true);
            $timestamp = $lastLogin !== '' ? strtotime($lastLogin) : false;
            if ($timestamp === false) {
                $unknown++;
            } elseif ($timestamp < $cutoff) {
                $stale++;
            }
        }

        if ($stale > 0) {
            return $this->finding('stale_privileged_accounts', 'medium', 'Stale privileged accounts', $stale . ' privileged account(s) have not logged in for at least 90 days.', $this->usersUrl(), 'warning');
        }
        if ($unknown > 0) {
            return $this->finding('stale_privileged_accounts', 'low', 'Stale privileged accounts', $unknown . ' privileged account(s) do not yet have local last-login evidence.', $this->usersUrl(), 'unavailable');
        }

        return $this->finding('stale_privileged_accounts', 'medium', 'Stale privileged accounts', 'No stale privileged accounts were found.', $this->usersUrl(), 'healthy');
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function inactiveComponentsFinding(): array
    {
        if (!function_exists('get_plugins') || !function_exists('wp_get_themes')) {
            return $this->finding('inactive_components', 'low', 'Inactive plugins and themes', 'Component activation evidence is unavailable.', $this->pluginsUrl(), 'unavailable');
        }

        $activePlugins = function_exists('get_option') ? (array) get_option('active_plugins', []) : [];
        $inactivePlugins = max(0, count(get_plugins()) - count($activePlugins));
        $themes = (array) wp_get_themes();
        $activeTheme = function_exists('get_stylesheet') ? (string) get_stylesheet() : '';
        $inactiveThemes = max(0, count($themes) - ($activeTheme !== '' ? 1 : 0));
        $total = $inactivePlugins + $inactiveThemes;

        return $this->finding(
            'inactive_components',
            'low',
            'Inactive plugins and themes',
            $total > 0 ? $inactivePlugins . ' inactive plugin(s) and ' . $inactiveThemes . ' inactive theme(s) are installed.' : 'No inactive plugins or themes were found.',
            $this->pluginsUrl(),
            $total > 0 ? 'warning' : 'healthy'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function excessiveSessionsFinding(): array
    {
        $users = 0;
        foreach ($this->loginSecurity->sessionReview(250) as $entry) {
            if ((int) ($entry['sessions'] ?? 0) > 5) {
                $users++;
            }
        }

        return $this->finding(
            'excessive_sessions',
            'medium',
            'Excessive privileged sessions',
            $users > 0 ? $users . ' protected user(s) have more than five active sessions.' : 'Protected users do not have excessive active sessions.',
            $this->loginSecurityUrl(),
            $users > 0 ? 'warning' : 'healthy'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function missingTwoFactorFinding(): array
    {
        $summary = $this->loginSecurity->summary();
        $required = (int) ($summary['required_users_total'] ?? 0);
        $pending = (int) ($summary['required_users_pending'] ?? 0);
        if (empty($summary['two_factor_required_roles'])) {
            return $this->finding('missing_required_2fa', 'medium', 'Required 2FA enrollment', 'No roles currently require 2FA.', $this->loginSecurityUrl(), 'informational');
        }

        return $this->finding(
            'missing_required_2fa',
            'high',
            'Required 2FA enrollment',
            $pending > 0 ? $pending . ' of ' . $required . ' required user(s) still need 2FA enrollment.' : 'All required users are enrolled in 2FA.',
            $this->loginSecurityUrl(),
            $pending > 0 ? 'warning' : 'healthy'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function provenanceFinding(): array
    {
        $summary = $this->provenance->summary();
        $unverified = (int) ($summary['unverified'] ?? 0);

        return $this->finding(
            'component_provenance',
            'medium',
            'Plugin and theme provenance',
            $unverified > 0 ? $unverified . ' installed component(s) have unverified provenance.' : 'All discovered components have recognized or administrator-trusted provenance.',
            $this->systemUrl() . '#shieldx-component-provenance',
            $unverified > 0 ? 'warning' : 'healthy'
        );
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function databasePrefixFinding(): array
    {
        global $table_prefix;
        $prefix = isset($table_prefix) && is_string($table_prefix) ? $table_prefix : '';
        if ($prefix === '') {
            return $this->finding('database_prefix', 'info', 'Database table prefix', 'Database prefix evidence is unavailable.', $this->systemUrl(), 'unavailable');
        }

        return $this->finding(
            'database_prefix',
            'info',
            'Database table prefix',
            $prefix === 'wp_' ? 'The default WordPress database prefix is in use. SHIELD-X does not rewrite prefixes automatically.' : 'A non-default WordPress database prefix is in use.',
            $this->systemUrl(),
            $prefix === 'wp_' ? 'informational' : 'healthy'
        );
    }

    /**
     * @param object $user
     */
    private function isPrivilegedUser($user): bool
    {
        if (function_exists('user_can') && user_can($user, 'manage_options')) {
            return true;
        }
        $roles = isset($user->roles) && is_array($user->roles) ? array_map('strval', $user->roles) : [];

        return in_array('administrator', $roles, true);
    }

    /**
     * @return array{id:string,severity:string,title:string,evidence:string,remediation_url:string,status:string}
     */
    private function finding(string $id, string $severity, string $title, string $evidence, string $url, string $status): array
    {
        return [
            'id' => sanitize_key($id),
            'severity' => in_array($severity, ['critical', 'high', 'medium', 'low', 'info'], true) ? $severity : 'info',
            'title' => sanitize_text_field($title),
            'evidence' => sanitize_text_field($evidence),
            'remediation_url' => function_exists('esc_url_raw') ? esc_url_raw($url) : $url,
            'status' => in_array($status, ['healthy', 'warning', 'informational', 'unavailable'], true) ? $status : 'unavailable',
        ];
    }

    private function systemUrl(): string
    {
        return function_exists('admin_url') ? admin_url('admin.php?page=shield-x-system') : '';
    }

    private function loginSecurityUrl(): string
    {
        return function_exists('admin_url') ? admin_url('admin.php?page=shield-x-login-security') : '';
    }

    private function usersUrl(): string
    {
        return function_exists('admin_url') ? admin_url('users.php') : '';
    }

    private function pluginsUrl(): string
    {
        return function_exists('admin_url') ? admin_url('plugins.php') : '';
    }

    private function generalUrl(): string
    {
        return function_exists('admin_url') ? admin_url('options-general.php') : '';
    }
}
