<?php

declare(strict_types=1);

namespace ShieldX\Core;

defined('ABSPATH') || exit;

final class NotificationSettings
{
    public const OPTION_NAME = 'shieldx_notification_settings';
    public const PROFILE_NORMAL = 'normal';
    public const PROFILE_EXTREME = 'extreme';
    public const PROFILE_PANIC = 'panic';
    public const DELIVERY_IMMEDIATE = 'immediate';
    public const DELIVERY_CRITICAL_ONLY = 'critical_only';
    public const DELIVERY_DIGEST = 'digest';
    public const DELIVERY_DIGEST_CRITICAL_IMMEDIATE = 'digest_critical_immediate';
    public const DIGEST_HOURLY = 'hourly';
    public const DIGEST_TWICE_DAILY = 'twicedaily';
    public const DIGEST_DAILY = 'daily';
    public const DIGEST_WEEKLY = 'weekly';
    public const DIGEST_EVERY = 'every';
    public const DIGEST_CUSTOM_RECURRENCE = 'shieldx_notification_digest_custom';

    /** @var array<string,mixed> */
    private $settings;

    /**
     * @param array<string,mixed> $settings
     */
    public function __construct(array $settings = [])
    {
        $this->settings = $this->normalize($settings);
    }

    public static function fromWordPress(): self
    {
        $cached = RequestCache::get(self::class);
        if ($cached instanceof self) {
            return $cached;
        }

        $stored = function_exists('get_option') ? get_option(self::OPTION_NAME, []) : [];

        $instance = new self(is_array($stored) ? $stored : []);
        RequestCache::rememberOption(self::class, self::OPTION_NAME, $instance);

        return $instance;
    }

    /**
     * @param array<string,mixed> $settings
     */
    public static function save(array $settings): self
    {
        $instance = new self($settings);
        if (function_exists('update_option')) {
            update_option(self::OPTION_NAME, $instance->all(), false);
        }

        RequestCache::rememberOption(self::class, self::OPTION_NAME, $instance);

        return $instance;
    }

    /**
     * @return array<string,mixed>
     */
    public function all(): array
    {
        return $this->settings;
    }

    /**
     * @return array<string,mixed>
     */
    public function export(): array
    {
        $settings = $this->settings;
        unset($settings['last_saved_at'], $settings['last_saved_by']);

        return $settings;
    }

    /**
     * @return array<string,mixed>
     */
    public function summary(): array
    {
        return [
            'profile' => $this->profile(),
            'enabled_events' => count($this->enabledEvents()),
            'available_events' => count(self::eventCatalog()),
            'recipients_configured' => count($this->recipients()),
            'delivery_mode' => $this->deliveryMode(),
            'digest_cadence' => $this->digestCadence(),
            'digest_custom_interval_minutes' => $this->digestCustomIntervalMinutes(),
            'last_saved_at' => (string) ($this->settings['last_saved_at'] ?? ''),
            'last_saved_by' => (int) ($this->settings['last_saved_by'] ?? 0),
        ];
    }

    public function profile(): string
    {
        return self::sanitizeProfile((string) ($this->settings['profile'] ?? self::PROFILE_NORMAL));
    }

    /**
     * @return array<int,string>
     */
    public function enabledEvents(): array
    {
        return array_values(array_filter((array) ($this->settings['enabled_events'] ?? []), 'is_string'));
    }

    /**
     * @return array<int,string>
     */
    public function recipients(): array
    {
        return array_values(array_filter((array) ($this->settings['recipients'] ?? []), 'is_string'));
    }

    public function deliveryMode(): string
    {
        return self::sanitizeDeliveryMode((string) ($this->settings['delivery_mode'] ?? self::DELIVERY_IMMEDIATE));
    }

    public function digestEnabled(): bool
    {
        return in_array($this->deliveryMode(), [self::DELIVERY_DIGEST, self::DELIVERY_DIGEST_CRITICAL_IMMEDIATE], true);
    }

    public function digestCadence(): string
    {
        return self::sanitizeDigestCadence((string) ($this->settings['digest_cadence'] ?? self::DIGEST_HOURLY));
    }

    public function digestCustomIntervalMinutes(): int
    {
        return self::sanitizeDigestCustomIntervalMinutes($this->settings['digest_custom_interval_minutes'] ?? 60);
    }

    public function digestRecurrence(): string
    {
        return $this->digestCadence() === self::DIGEST_EVERY ? self::DIGEST_CUSTOM_RECURRENCE : $this->digestCadence();
    }

    public function isEnabled(string $eventKey): bool
    {
        return in_array($eventKey, $this->enabledEvents(), true);
    }

    public static function sanitizeProfile(string $profile): string
    {
        $profile = function_exists('sanitize_key') ? sanitize_key($profile) : strtolower(preg_replace('/[^a-z0-9_\-]/', '', $profile) ?: '');

        return array_key_exists($profile, self::profiles()) ? $profile : self::PROFILE_NORMAL;
    }

    public static function sanitizeDeliveryMode(string $mode): string
    {
        $mode = function_exists('sanitize_key') ? sanitize_key($mode) : strtolower(preg_replace('/[^a-z0-9_\-]/', '', $mode) ?: '');

        return in_array($mode, [self::DELIVERY_IMMEDIATE, self::DELIVERY_CRITICAL_ONLY, self::DELIVERY_DIGEST, self::DELIVERY_DIGEST_CRITICAL_IMMEDIATE], true) ? $mode : self::DELIVERY_IMMEDIATE;
    }

    public static function sanitizeDigestCadence(string $cadence): string
    {
        $cadence = function_exists('sanitize_key') ? sanitize_key($cadence) : strtolower(preg_replace('/[^a-z0-9_\-]/', '', $cadence) ?: '');

        return array_key_exists($cadence, self::digestCadenceOptions()) ? $cadence : self::DIGEST_HOURLY;
    }

    /**
     * @param mixed $minutes
     */
    public static function sanitizeDigestCustomIntervalMinutes($minutes): int
    {
        return max(5, min(1440, (int) $minutes));
    }

    /**
     * @return array<string,string>
     */
    public static function digestCadenceOptions(): array
    {
        return [
            self::DIGEST_HOURLY => __('Hourly', 'xerotact-shield-x-security'),
            self::DIGEST_TWICE_DAILY => __('Twice daily', 'xerotact-shield-x-security'),
            self::DIGEST_DAILY => __('Daily', 'xerotact-shield-x-security'),
            self::DIGEST_WEEKLY => __('Weekly', 'xerotact-shield-x-security'),
            self::DIGEST_EVERY => __('Every', 'xerotact-shield-x-security'),
        ];
    }

    /**
     * @return array<string,array{label:string,summary:string,events:array<int,string>}>
     */
    public static function profiles(): array
    {
        $catalog = self::eventCatalog();
        $profiles = [
            self::PROFILE_NORMAL => [
                'label' => __('Normal', 'xerotact-shield-x-security'),
                'summary' => __('Critical operational and account-safety notifications.', 'xerotact-shield-x-security'),
                'events' => [
                    'scanner.high_finding',
                    'scanner.scan_failed',
                    'login.lockout',
                    'login.recovery_used',
                    'hardening.lockdown_failure',
                    'hardening.lockdown_reverted',
                    'backup.restore_failed',
                    'backup.backup_failed',
                    'system.settings_changed',
                    'system.setup_mode_applied',
                ],
            ],
            self::PROFILE_EXTREME => [
                'label' => __('Extreme', 'xerotact-shield-x-security'),
                'summary' => __('Normal profile plus active blocking, medium findings, and configuration changes.', 'xerotact-shield-x-security'),
                'events' => [
                    'scanner.high_finding',
                    'scanner.medium_finding',
                    'scanner.db_finding',
                    'scanner.vulnerability_finding',
                    'scanner.scan_failed',
                    'firewall.waf_block',
                    'firewall.waf_challenge',
                    'login.lockout',
                    'login.failure_burst',
                    'login.2fa_disabled',
                    'login.2fa_reset',
                    'login.recovery_used',
                    'backup.restore_point_created',
                    'backup.restore_restored',
                    'backup.backup_failed',
                    'backup.restore_failed',
                    'hardening.hardening_applied',
                    'hardening.lockdown_applied',
                    'hardening.lockdown_reverted',
                    'hardening.lockdown_failure',
                    'hardening.lockdown_policy_changed',
                    'system.human_challenge_changed',
                    'system.settings_changed',
                    'system.setup_mode_applied',
                ],
            ],
            self::PROFILE_PANIC => [
                'label' => __('Panic', 'xerotact-shield-x-security'),
                'summary' => __('All high-signal events plus throttled noisy activity while responding to an incident.', 'xerotact-shield-x-security'),
                'events' => array_keys($catalog),
            ],
        ];

        foreach (AddonRegistry::fromWordPress()->notificationEvents() as $eventKey => $event) {
            foreach ((array) ($event['profiles'] ?? []) as $profile) {
                $profileKey = (string) $profile;
                if ($profileKey === self::PROFILE_PANIC || !isset($profiles[$profileKey])) {
                    continue;
                }

                if (!in_array($eventKey, $profiles[$profileKey]['events'], true)) {
                    $profiles[$profileKey]['events'][] = $eventKey;
                }
            }
        }

        $profiles[self::PROFILE_PANIC]['events'] = array_keys($catalog);

        return $profiles;
    }

    /**
     * @return array<string,array{label:string,group:string,description:string,noisy:bool,critical:bool}>
     */
    public static function eventCatalog(): array
    {
        $events = [
            'firewall.waf_block' => self::event(__('WAF block', 'xerotact-shield-x-security'), __('Firewall', 'xerotact-shield-x-security'), __('A request was blocked by the firewall.', 'xerotact-shield-x-security'), false, false),
            'firewall.waf_challenge' => self::event(__('WAF challenge', 'xerotact-shield-x-security'), __('Firewall', 'xerotact-shield-x-security'), __('A request was challenged by the firewall.', 'xerotact-shield-x-security'), false, false),
            'firewall.waf_high_confidence_hit' => self::event(__('High-confidence WAF hit', 'xerotact-shield-x-security'), __('Firewall', 'xerotact-shield-x-security'), __('A high scoring WAF rule matched a request.', 'xerotact-shield-x-security'), true, false),
            'firewall.waf_match' => self::event(__('All WAF matches', 'xerotact-shield-x-security'), __('Firewall', 'xerotact-shield-x-security'), __('Every WAF rule match, throttled to reduce noise.', 'xerotact-shield-x-security'), true, false),
            'firewall.reputation_match' => self::event(__('Reputation match', 'xerotact-shield-x-security'), __('Firewall', 'xerotact-shield-x-security'), __('A request matched a reputation rule or feed.', 'xerotact-shield-x-security'), true, false),

            'login.lockout' => self::event(__('Login lockout', 'xerotact-shield-x-security'), __('Login Security', 'xerotact-shield-x-security'), __('A user or IP was locked out after repeated failures.', 'xerotact-shield-x-security'), false, true),
            'login.failure_burst' => self::event(__('Login failure burst', 'xerotact-shield-x-security'), __('Login Security', 'xerotact-shield-x-security'), __('Repeated login failures were detected.', 'xerotact-shield-x-security'), true, false),
            'login.2fa_disabled' => self::event(__('2FA disabled', 'xerotact-shield-x-security'), __('Login Security', 'xerotact-shield-x-security'), __('Authenticator 2FA was disabled for an account.', 'xerotact-shield-x-security'), false, true),
            'login.2fa_reset' => self::event(__('2FA reset', 'xerotact-shield-x-security'), __('Login Security', 'xerotact-shield-x-security'), __('A 2FA reset flow was requested or completed.', 'xerotact-shield-x-security'), false, true),
            'login.recovery_used' => self::event(__('Recovery URL used', 'xerotact-shield-x-security'), __('Login Security', 'xerotact-shield-x-security'), __('The emergency recovery flow was used.', 'xerotact-shield-x-security'), false, true),

            'scanner.scan_failed' => self::event(__('Scan failure', 'xerotact-shield-x-security'), __('Scanners', 'xerotact-shield-x-security'), __('A scheduled or manual scan could not complete.', 'xerotact-shield-x-security'), false, false),
            'scanner.high_finding' => self::event(__('Critical/high scanner finding', 'xerotact-shield-x-security'), __('Scanners', 'xerotact-shield-x-security'), __('A scanner produced a critical or high severity finding.', 'xerotact-shield-x-security'), false, true),
            'scanner.medium_finding' => self::event(__('Medium scanner finding', 'xerotact-shield-x-security'), __('Scanners', 'xerotact-shield-x-security'), __('A scanner produced a medium severity finding.', 'xerotact-shield-x-security'), true, false),
            'scanner.db_finding' => self::event(__('Database suspicious finding', 'xerotact-shield-x-security'), __('Scanners', 'xerotact-shield-x-security'), __('The database scanner detected suspicious stored content.', 'xerotact-shield-x-security'), false, true),
            'scanner.vulnerability_finding' => self::event(__('Vulnerability finding', 'xerotact-shield-x-security'), __('Scanners', 'xerotact-shield-x-security'), __('The vulnerability scanner detected an advisory or risky version.', 'xerotact-shield-x-security'), false, true),
            'scanner.scan_completed' => self::event(__('Scan completed', 'xerotact-shield-x-security'), __('Scanners', 'xerotact-shield-x-security'), __('A scan completed successfully.', 'xerotact-shield-x-security'), true, false),

            'backup.restore_point_created' => self::event(__('Restore point created', 'xerotact-shield-x-security'), __('Backup/Restore', 'xerotact-shield-x-security'), __('A restore point was created.', 'xerotact-shield-x-security'), true, false),
            'backup.restore_failed' => self::event(__('Restore failed', 'xerotact-shield-x-security'), __('Backup/Restore', 'xerotact-shield-x-security'), __('A restore operation failed.', 'xerotact-shield-x-security'), false, true),
            'backup.restore_restored' => self::event(__('Restore point restored', 'xerotact-shield-x-security'), __('Backup/Restore', 'xerotact-shield-x-security'), __('A restore point was applied.', 'xerotact-shield-x-security'), false, false),
            'backup.backup_completed' => self::event(__('Backup completed', 'xerotact-shield-x-security'), __('Backup/Restore', 'xerotact-shield-x-security'), __('A scheduled database backup completed.', 'xerotact-shield-x-security'), true, false),
            'backup.backup_failed' => self::event(__('Backup failed', 'xerotact-shield-x-security'), __('Backup/Restore', 'xerotact-shield-x-security'), __('A database or file backup failed.', 'xerotact-shield-x-security'), false, false),
            'backup.shadow_changes' => self::event(__('Shadow changes awaiting review', 'xerotact-shield-x-security'), __('Backup/Restore', 'xerotact-shield-x-security'), __('A shadow-copy session found changed files.', 'xerotact-shield-x-security'), true, false),

            'hardening.lockdown_applied' => self::event(__('Lockdown applied', 'xerotact-shield-x-security'), __('Hardening/Lockdown', 'xerotact-shield-x-security'), __('Filesystem lockdown was applied.', 'xerotact-shield-x-security'), false, false),
            'hardening.lockdown_reverted' => self::event(__('Lockdown reverted', 'xerotact-shield-x-security'), __('Hardening/Lockdown', 'xerotact-shield-x-security'), __('Filesystem lockdown was reverted.', 'xerotact-shield-x-security'), false, false),
            'hardening.lockdown_failure' => self::event(__('Lockdown failure', 'xerotact-shield-x-security'), __('Hardening/Lockdown', 'xerotact-shield-x-security'), __('A lockdown action failed.', 'xerotact-shield-x-security'), false, false),
            'hardening.lockdown_policy_changed' => self::event(__('Lockdown policy changed', 'xerotact-shield-x-security'), __('Hardening/Lockdown', 'xerotact-shield-x-security'), __('The lockdown policy was saved or changed.', 'xerotact-shield-x-security'), true, false),
            'hardening.hardening_applied' => self::event(__('Hardening applied', 'xerotact-shield-x-security'), __('Hardening/Lockdown', 'xerotact-shield-x-security'), __('A hardening configuration was saved.', 'xerotact-shield-x-security'), true, false),

            'system.settings_changed' => self::event(__('Settings import/export', 'xerotact-shield-x-security'), __('System/Configuration', 'xerotact-shield-x-security'), __('Settings were exported or imported.', 'xerotact-shield-x-security'), false, false),
            'system.setup_mode_applied' => self::event(__('Setup mode applied', 'xerotact-shield-x-security'), __('System/Configuration', 'xerotact-shield-x-security'), __('An auto setup profile was applied.', 'xerotact-shield-x-security'), false, false),
            'system.human_challenge_changed' => self::event(__('Human challenge changed', 'xerotact-shield-x-security'), __('System/Configuration', 'xerotact-shield-x-security'), __('Cap.js Challenge (Captcha) settings changed.', 'xerotact-shield-x-security'), true, false),
            'system.compatibility_warning' => self::event(__('System compatibility warning', 'xerotact-shield-x-security'), __('System/Configuration', 'xerotact-shield-x-security'), __('A required SHIELD-X runtime dependency is missing or degraded.', 'xerotact-shield-x-security'), true, false),
            'system.log_rotation' => self::event(__('Log rotation / cleanup', 'xerotact-shield-x-security'), __('System/Configuration', 'xerotact-shield-x-security'), __('A log rotation or temporary cleanup event occurred.', 'xerotact-shield-x-security'), true, false),
            'system.update_coordination' => self::event(__('Update coordination', 'xerotact-shield-x-security'), __('System/Configuration', 'xerotact-shield-x-security'), __('Lockdown was suspended, resumed, or rolled back around updates.', 'xerotact-shield-x-security'), false, false),
        ];

        foreach (AddonRegistry::fromWordPress()->notificationEvents() as $key => $event) {
            unset($event['profiles']);
            $events[$key] = $event;
        }

        return $events;
    }

    /**
     * @return array<string,array<int,string>>
     */
    public static function groupedEvents(): array
    {
        $groups = [];
        foreach (self::eventCatalog() as $key => $event) {
            $group = (string) $event['group'];
            if (!isset($groups[$group])) {
                $groups[$group] = [];
            }
            $groups[$group][] = $key;
        }

        return $groups;
    }

    /**
     * @param mixed $emails
     * @return array<int,string>
     */
    public static function sanitizeEmails($emails): array
    {
        $lines = is_array($emails) ? $emails : preg_split('/[\r\n,]+/', (string) $emails);
        $valid = [];

        foreach ((array) $lines as $line) {
            if (!is_scalar($line)) {
                continue;
            }

            $email = self::sanitizeEmail((string) $line);
            if ($email !== '' && !in_array($email, $valid, true)) {
                $valid[] = $email;
            }
        }

        return $valid;
    }

    /**
     * @param array<string,mixed> $settings
     * @return array<string,mixed>
     */
    private function normalize(array $settings): array
    {
        $profile = self::sanitizeProfile((string) ($settings['profile'] ?? self::PROFILE_NORMAL));
        $events = isset($settings['enabled_events'])
            ? $this->sanitizeEventKeys((array) $settings['enabled_events'])
            : self::profiles()[$profile]['events'];

        return [
            'profile' => $profile,
            'enabled_events' => $events,
            'recipients' => self::sanitizeEmails($settings['recipients'] ?? []),
            'delivery_mode' => self::sanitizeDeliveryMode((string) ($settings['delivery_mode'] ?? self::DELIVERY_IMMEDIATE)),
            'digest_cadence' => self::sanitizeDigestCadence((string) ($settings['digest_cadence'] ?? self::DIGEST_HOURLY)),
            'digest_custom_interval_minutes' => self::sanitizeDigestCustomIntervalMinutes($settings['digest_custom_interval_minutes'] ?? 60),
            'last_saved_at' => is_scalar($settings['last_saved_at'] ?? null) ? (string) $settings['last_saved_at'] : '',
            'last_saved_by' => max(0, (int) ($settings['last_saved_by'] ?? 0)),
        ];
    }

    /**
     * @param array<int,mixed> $events
     * @return array<int,string>
     */
    private function sanitizeEventKeys(array $events): array
    {
        $allowed = array_fill_keys(array_keys(self::eventCatalog()), true);
        $sanitized = [];

        foreach ($events as $event) {
            if (!is_scalar($event)) {
                continue;
            }

            $key = (string) $event;
            if (isset($allowed[$key]) && !in_array($key, $sanitized, true)) {
                $sanitized[] = $key;
            }
        }

        return $sanitized;
    }

    /**
     * @return array{label:string,group:string,description:string,noisy:bool,critical:bool}
     */
    private static function event(string $label, string $group, string $description, bool $noisy, bool $critical): array
    {
        return [
            'label' => $label,
            'group' => $group,
            'description' => $description,
            'noisy' => $noisy,
            'critical' => $critical,
        ];
    }

    private static function sanitizeEmail(string $email): string
    {
        $email = trim($email);
        $sanitized = function_exists('sanitize_email') ? sanitize_email($email) : filter_var($email, FILTER_SANITIZE_EMAIL);
        $sanitized = is_string($sanitized) ? $sanitized : '';
        if ($sanitized === '') {
            return '';
        }

        if (function_exists('is_email')) {
            return is_email($sanitized) ? $sanitized : '';
        }

        return filter_var($sanitized, FILTER_VALIDATE_EMAIL) ? $sanitized : '';
    }
}
