<?php

declare(strict_types=1);

namespace ShieldX\Maintenance;

defined('ABSPATH') || exit;

use ShieldX\Core\RequestCache;
use ShieldX\Support\ExternalRunnerToken;

final class ScheduledRestorePointSettings
{
    public const OPTION_NAME = 'shieldx_restore_point_schedule';

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

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

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

        $settings = function_exists('get_option') ? get_option(self::OPTION_NAME, []) : [];
        $instance = new self(is_array($settings) ? $settings : []);

        if (function_exists('update_option') && (!is_array($settings) || $settings !== $instance->settings)) {
            update_option(self::OPTION_NAME, $instance->settings, false);
        }

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

        return $instance;
    }

    /**
     * @return array<string,mixed>
     */
    public function all(): array
    {
        $settings = $this->settings;
        $settings['external_token'] = $this->externalToken();
        $settings['external_url'] = $this->externalUrl();

        return $settings;
    }

    public function enabled(): bool
    {
        return (bool) ($this->settings['enabled'] ?? false);
    }

    public function recurrence(): string
    {
        return (string) ($this->settings['recurrence'] ?? 'daily');
    }

    public function includeDatabase(): bool
    {
        return (bool) ($this->settings['include_database'] ?? true);
    }

    public function emailReportsEnabled(): bool
    {
        return (bool) ($this->settings['email_reports_enabled'] ?? false);
    }

    public function alertOnWarning(): bool
    {
        return (bool) ($this->settings['alert_on_warning'] ?? true);
    }

    public function alertOnFailure(): bool
    {
        return (bool) ($this->settings['alert_on_failure'] ?? true);
    }

    public function externalRunnerEnabled(): bool
    {
        return (bool) ($this->settings['external_runner_enabled'] ?? false);
    }

    public function externalToken(): string
    {
        return ExternalRunnerToken::reveal((string) ($this->settings['external_token'] ?? ''));
    }

    public function externalUrl(): string
    {
        return ExternalRunnerToken::endpoint('/autorestorepoint');
    }

    public function matchesExternalToken(string $token): bool
    {
        if (ExternalRunnerToken::matches($token, (string) ($this->settings['external_token_hash'] ?? ''))) {
            return true;
        }

        $expected = $this->externalToken();
        return $expected !== '' && hash_equals($expected, trim($token));
    }

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

    /**
     * @param array<string,mixed> $settings
     */
    public function save(array $settings): void
    {
        $this->settings = self::normalize(array_merge($this->settings, $settings));

        if (function_exists('update_option')) {
            update_option(self::OPTION_NAME, $this->settings, false);
        }

        RequestCache::rememberOption(self::class, self::OPTION_NAME, $this);
    }

    /**
     * @param array<string,mixed> $result
     */
    public function recordRun(array $result, bool $notified): void
    {
        $this->settings = self::normalize(array_merge($this->settings, [
            'last_run_at' => self::now(),
            'last_restore_id' => (string) ($result['restore_id'] ?? ''),
            'last_status' => (string) ($result['status'] ?? 'unknown'),
            'last_error' => (string) ($result['error'] ?? ''),
            'last_report_sent_at' => $notified ? self::now() : (string) ($this->settings['last_report_sent_at'] ?? ''),
            'last_files' => max(0, (int) ($result['files'] ?? 0)),
            'last_bytes_exported' => max(0, (int) ($result['bytes_exported'] ?? 0)),
            'last_database_backup_id' => (string) ($result['database_backup_id'] ?? ''),
            'last_warning_count' => max(0, (int) ($result['warning_count'] ?? 0)),
        ]));

        if (function_exists('update_option')) {
            update_option(self::OPTION_NAME, $this->settings, false);
        }

        RequestCache::rememberOption(self::class, self::OPTION_NAME, $this);
    }

    /**
     * @param array<string,mixed> $settings
     * @return array<string,mixed>
     */
    private static function normalize(array $settings): array
    {
        $externalToken = self::externalTokenStorage($settings);

        return [
            'enabled' => array_key_exists('enabled', $settings) ? self::toBool($settings['enabled']) : false,
            'recurrence' => self::sanitizeRecurrence((string) ($settings['recurrence'] ?? 'daily')),
            'include_database' => array_key_exists('include_database', $settings) ? self::toBool($settings['include_database']) : true,
            'email_reports_enabled' => array_key_exists('email_reports_enabled', $settings) ? self::toBool($settings['email_reports_enabled']) : false,
            'alert_on_warning' => array_key_exists('alert_on_warning', $settings) ? self::toBool($settings['alert_on_warning']) : true,
            'alert_on_failure' => array_key_exists('alert_on_failure', $settings) ? self::toBool($settings['alert_on_failure']) : true,
            'external_runner_enabled' => array_key_exists('external_runner_enabled', $settings) ? self::toBool($settings['external_runner_enabled']) : false,
            'external_token' => $externalToken['external_token'],
            'external_token_hash' => $externalToken['external_token_hash'],
            'recipients' => self::cleanRecipients($settings['recipients'] ?? []),
            'last_run_at' => self::cleanText((string) ($settings['last_run_at'] ?? '')),
            'last_restore_id' => self::cleanText((string) ($settings['last_restore_id'] ?? '')),
            'last_status' => self::cleanText((string) ($settings['last_status'] ?? '')),
            'last_error' => self::cleanText((string) ($settings['last_error'] ?? '')),
            'last_report_sent_at' => self::cleanText((string) ($settings['last_report_sent_at'] ?? '')),
            'last_files' => max(0, (int) ($settings['last_files'] ?? 0)),
            'last_bytes_exported' => max(0, (int) ($settings['last_bytes_exported'] ?? 0)),
            'last_database_backup_id' => self::cleanText((string) ($settings['last_database_backup_id'] ?? '')),
            'last_warning_count' => max(0, (int) ($settings['last_warning_count'] ?? 0)),
        ];
    }

    /**
     * @param mixed $value
     */
    private static function toBool($value): bool
    {
        if (is_bool($value)) {
            return $value;
        }

        return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
    }

    private static function sanitizeRecurrence(string $recurrence): string
    {
        return in_array($recurrence, ['hourly', 'twicedaily', 'daily', 'weekly'], true) ? $recurrence : 'daily';
    }

    /**
     * @param mixed $value
     * @return array<int,string>
     */
    private static function cleanRecipients($value): array
    {
        if (is_string($value)) {
            $value = preg_split('~[\r\n,;]+~', $value) ?: [];
        }

        if (!is_array($value)) {
            return [];
        }

        $clean = [];

        foreach ($value as $email) {
            $email = trim((string) $email);
            $sanitized = function_exists('sanitize_email') ? sanitize_email($email) : $email;

            if ($sanitized !== '' && filter_var($sanitized, FILTER_VALIDATE_EMAIL)) {
                $clean[$sanitized] = $sanitized;
            }
        }

        ksort($clean);

        return array_values($clean);
    }

    private static function cleanText(string $value): string
    {
        $value = function_exists('wp_strip_all_tags')
            ? wp_strip_all_tags($value)
            : (preg_replace('/<[^>]*>/', '', $value) ?: '');
        $value = trim($value);
        $value = preg_replace('/[\x00-\x1F\x7F]+/', ' ', $value) ?: '';

        return substr(trim($value), 0, 255);
    }

    /**
     * @param array<string,mixed> $settings
     * @return array{external_token:string,external_token_hash:string}
     */
    private static function externalTokenStorage(array $settings): array
    {
        return ExternalRunnerToken::storageValues(
            (string) ($settings['external_token'] ?? ''),
            (string) ($settings['external_token_hash'] ?? '')
        );
    }

    private static function now(): string
    {
        return function_exists('current_time') ? current_time('mysql', true) : gmdate('Y-m-d H:i:s');
    }
}
