<?php

declare(strict_types=1);

namespace ShieldX\Maintenance;

defined('ABSPATH') || exit;

use ShieldX\Core\RequestCache;
use ShieldX\Support\CryptoBox;

final class GoogleDriveSettings
{
    public const OPTION_NAME = 'shieldx_google_drive_settings';
    public const STATUS_DISCONNECTED = 'disconnected';
    public const STATUS_PENDING = 'pending';
    public const STATUS_CONNECTED = 'connected';
    public const STATUS_ERROR = 'error';
    public const DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive.file';

    private const ENCRYPTED_PREFIX = 'enc:v2:';
    private const ACCESS_TOKEN_TRANSIENT_PREFIX = 'shieldx_google_drive_access_';
    private const ACCESS_TOKEN_TTL_MARGIN_SECONDS = 60;
    private const ACCESS_TOKEN_MAX_TTL_SECONDS = 3600;
    private const BUNDLED_CLIENT_ID = '';
    private const BUNDLED_CLIENT_SECRET = '';

    /** @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;
        }

        $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;
    }

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

    /**
     * @return array<string,mixed>
     */
    public function summary(): array
    {
        return [
            'status' => $this->status(),
            'connected' => $this->connected(),
            'credentials_available' => $this->credentialsAvailable(),
            'local_oauth_configured' => $this->localOAuthConfigured(),
            'secret_storage_ready' => self::secretStorageReady(),
            'account_email' => (string) ($this->settings['account_email'] ?? ''),
            'last_status' => (string) ($this->settings['last_status'] ?? ''),
            'last_error' => (string) ($this->settings['last_error'] ?? ''),
            'last_sync_at' => (string) ($this->settings['last_sync_at'] ?? ''),
            'pending' => $this->pendingSummary(),
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public function pendingSummary(): array
    {
        return [
            'user_code' => (string) ($this->settings['device_user_code'] ?? ''),
            'verification_url' => (string) ($this->settings['device_verification_url'] ?? ''),
            'expires_at' => (string) ($this->settings['device_expires_at'] ?? ''),
            'interval' => (int) ($this->settings['device_interval'] ?? 5),
        ];
    }

    public function clientId(): string
    {
        $clientId = $this->localClientId();
        if ($clientId === '') {
            $clientId = defined('SHIELDX_GOOGLE_DRIVE_CLIENT_ID') ? (string) constant('SHIELDX_GOOGLE_DRIVE_CLIENT_ID') : self::BUNDLED_CLIENT_ID;
        }

        if ($clientId === '' && function_exists('getenv')) {
            $envClientId = getenv('SHIELDX_GOOGLE_DRIVE_CLIENT_ID');
            $clientId = is_string($envClientId) ? $envClientId : '';
        }

        if (function_exists('apply_filters')) {
            try {
                $clientId = (string) apply_filters('shieldx_google_drive_client_id', $clientId);
            } catch (\Throwable $error) {
                \ShieldX\Support\ErrorLog::write('SHIELD-X Google Drive client id filter failed: ' . $error->getMessage());
            }
        }

        return self::cleanCredential($clientId);
    }

    public function clientSecret(): string
    {
        $clientSecret = $this->localClientSecret();
        if ($clientSecret === '') {
            $clientSecret = defined('SHIELDX_GOOGLE_DRIVE_CLIENT_SECRET') ? (string) constant('SHIELDX_GOOGLE_DRIVE_CLIENT_SECRET') : self::BUNDLED_CLIENT_SECRET;
        }

        if ($clientSecret === '' && function_exists('getenv')) {
            $envClientSecret = getenv('SHIELDX_GOOGLE_DRIVE_CLIENT_SECRET');
            $clientSecret = is_string($envClientSecret) ? $envClientSecret : '';
        }

        if (function_exists('apply_filters')) {
            try {
                $clientSecret = (string) apply_filters('shieldx_google_drive_client_secret', $clientSecret);
            } catch (\Throwable $error) {
                \ShieldX\Support\ErrorLog::write('SHIELD-X Google Drive client secret filter failed: ' . $error->getMessage());
            }
        }

        return self::cleanCredential($clientSecret);
    }

    public function credentialsAvailable(): bool
    {
        return $this->clientId() !== '' && $this->clientSecret() !== '';
    }

    public function localOAuthConfigured(): bool
    {
        return $this->localClientId() !== '' && $this->localClientSecret() !== '';
    }

    public function localClientId(): string
    {
        return self::unprotectSecret((string) ($this->settings['oauth_client_id'] ?? ''));
    }

    public function localClientSecret(): string
    {
        return self::unprotectSecret((string) ($this->settings['oauth_client_secret'] ?? ''));
    }

    public function status(): string
    {
        return self::sanitizeStatus((string) ($this->settings['status'] ?? self::STATUS_DISCONNECTED));
    }

    public function connected(): bool
    {
        return $this->status() === self::STATUS_CONNECTED && $this->refreshToken() !== '';
    }

    public function refreshToken(): string
    {
        return self::unprotectSecret((string) ($this->settings['refresh_token'] ?? ''));
    }

    public function deviceCode(): string
    {
        return self::unprotectSecret((string) ($this->settings['device_code'] ?? ''));
    }

    public function rootFolderId(): string
    {
        return self::cleanText((string) ($this->settings['root_folder_id'] ?? ''));
    }

    public function cachedAccessToken(): string
    {
        if (!function_exists('get_transient')) {
            return '';
        }

        $cacheKey = $this->accessTokenCacheKey();
        if ($cacheKey === '') {
            return '';
        }

        $stored = get_transient($cacheKey);
        if (!is_string($stored) || $stored === '') {
            return '';
        }

        $token = self::unprotectAccessToken($stored);
        if ($token === '') {
            $this->clearAccessTokenCache();
        }

        return $token;
    }

    public function cacheAccessToken(string $accessToken, int $expiresIn): void
    {
        if (!function_exists('set_transient')) {
            return;
        }

        $cacheKey = $this->accessTokenCacheKey();
        $accessToken = self::cleanAccessToken($accessToken);
        $ttl = min(max(0, $expiresIn) - self::ACCESS_TOKEN_TTL_MARGIN_SECONDS, self::ACCESS_TOKEN_MAX_TTL_SECONDS);
        if ($cacheKey === '' || $accessToken === '' || $ttl <= 0) {
            $this->clearAccessTokenCache();
            return;
        }

        $protected = self::protectAccessToken($accessToken);
        if ($protected === '') {
            return;
        }

        set_transient($cacheKey, $protected, $ttl);
    }

    public function clearAccessTokenCache(): void
    {
        if (!function_exists('delete_transient')) {
            return;
        }

        $cacheKey = $this->accessTokenCacheKey();
        if ($cacheKey !== '') {
            delete_transient($cacheKey);
        }
    }

    /**
     * @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);
    }

    public function saveOAuthCredentials(string $clientId, string $clientSecret): bool
    {
        $clientId = self::cleanCredential($clientId);
        $clientSecret = self::cleanCredential($clientSecret);
        if ($clientId === '' || $clientSecret === '' || !self::secretStorageReady()) {
            return false;
        }

        $this->save([
            'oauth_client_id' => self::protectSecret($clientId),
            'oauth_client_secret' => self::protectSecret($clientSecret),
            'status' => self::STATUS_DISCONNECTED,
            'refresh_token' => '',
            'account_email' => '',
            'root_folder_id' => '',
            'device_code' => '',
            'device_user_code' => '',
            'device_verification_url' => '',
            'device_expires_at' => '',
            'device_interval' => 5,
            'last_status' => 'oauth_app_saved',
            'last_error' => '',
            'last_sync_at' => self::now(),
        ]);

        return $this->localOAuthConfigured();
    }

    /**
     * @param array<string,mixed> $payload
     */
    public function saveDeviceAuthorization(array $payload): void
    {
        $expiresIn = max(60, min(3600, (int) ($payload['expires_in'] ?? 1800)));
        $this->save([
            'status' => self::STATUS_PENDING,
            'device_code' => self::protectSecret((string) ($payload['device_code'] ?? '')),
            'device_user_code' => self::cleanText((string) ($payload['user_code'] ?? '')),
            'device_verification_url' => self::cleanUrl((string) ($payload['verification_url'] ?? '')),
            'device_expires_at' => gmdate('c', time() + $expiresIn),
            'device_interval' => max(1, min(30, (int) ($payload['interval'] ?? 5))),
            'last_status' => 'authorization_pending',
            'last_error' => '',
        ]);
    }

    public function saveConnected(string $refreshToken, string $accountEmail, string $rootFolderId = ''): void
    {
        $this->clearAccessTokenCache();

        $this->save([
            'status' => self::STATUS_CONNECTED,
            'refresh_token' => self::protectSecret($refreshToken),
            'account_email' => self::cleanText($accountEmail),
            'root_folder_id' => self::cleanText($rootFolderId),
            'device_code' => '',
            'device_user_code' => '',
            'device_verification_url' => '',
            'device_expires_at' => '',
            'device_interval' => 5,
            'last_status' => 'connected',
            'last_error' => '',
            'last_sync_at' => self::now(),
        ]);
    }

    public function recordStatus(string $status, string $error = ''): void
    {
        $this->save([
            'last_status' => self::cleanText($status),
            'last_error' => self::cleanText($error),
            'last_sync_at' => self::now(),
            'status' => $this->status(),
        ]);
    }

    public function disconnect(): void
    {
        $this->clearAccessTokenCache();

        $this->settings = self::normalize([
            'oauth_client_id' => (string) ($this->settings['oauth_client_id'] ?? ''),
            'oauth_client_secret' => (string) ($this->settings['oauth_client_secret'] ?? ''),
            'last_status' => 'disconnected',
            'last_sync_at' => self::now(),
        ]);

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

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

    public static function secretStorageReady(): bool
    {
        return CryptoBox::isAvailable()
            && function_exists('wp_salt')
            && (string) wp_salt('auth') !== '';
    }

    /**
     * @param array<string,mixed> $settings
     * @return array<string,mixed>
     */
    private static function normalize(array $settings): array
    {
        $deviceCode = (string) ($settings['device_code'] ?? '');
        $refreshToken = (string) ($settings['refresh_token'] ?? '');
        $oauthClientId = (string) ($settings['oauth_client_id'] ?? '');
        $oauthClientSecret = (string) ($settings['oauth_client_secret'] ?? '');

        return [
            'status' => self::sanitizeStatus((string) ($settings['status'] ?? self::STATUS_DISCONNECTED)),
            'oauth_client_id' => self::isProtectedSecret($oauthClientId) ? $oauthClientId : self::protectSecret($oauthClientId),
            'oauth_client_secret' => self::isProtectedSecret($oauthClientSecret) ? $oauthClientSecret : self::protectSecret($oauthClientSecret),
            'refresh_token' => self::isProtectedSecret($refreshToken) ? $refreshToken : self::protectSecret($refreshToken),
            'account_email' => self::cleanText((string) ($settings['account_email'] ?? '')),
            'root_folder_id' => self::cleanText((string) ($settings['root_folder_id'] ?? '')),
            'device_code' => self::isProtectedSecret($deviceCode) ? $deviceCode : self::protectSecret($deviceCode),
            'device_user_code' => self::cleanText((string) ($settings['device_user_code'] ?? '')),
            'device_verification_url' => self::cleanUrl((string) ($settings['device_verification_url'] ?? '')),
            'device_expires_at' => self::cleanText((string) ($settings['device_expires_at'] ?? '')),
            'device_interval' => max(1, min(30, (int) ($settings['device_interval'] ?? 5))),
            'last_status' => self::cleanText((string) ($settings['last_status'] ?? '')),
            'last_error' => self::cleanText((string) ($settings['last_error'] ?? '')),
            'last_sync_at' => self::cleanText((string) ($settings['last_sync_at'] ?? '')),
        ];
    }

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

        return in_array($status, [self::STATUS_DISCONNECTED, self::STATUS_PENDING, self::STATUS_CONNECTED, self::STATUS_ERROR], true) ? $status : self::STATUS_DISCONNECTED;
    }

    private static function cleanCredential(string $value): string
    {
        $value = trim($value);
        $value = preg_replace('/[\x00-\x1F\x7F]+/', '', $value) ?: '';

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

    private static function cleanAccessToken(string $value): string
    {
        $value = trim($value);
        $value = preg_replace('/[\x00-\x1F\x7F]+/', '', $value) ?: '';

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

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

        return substr($value, 0, 512);
    }

    private static function cleanUrl(string $value): string
    {
        $value = self::cleanText($value);

        if ($value === '') {
            return '';
        }

        if (function_exists('esc_url_raw')) {
            return esc_url_raw($value, ['https']);
        }

        return preg_match('~^https://~i', $value) === 1 ? $value : '';
    }

    private static function protectSecret(string $secret): string
    {
        $secret = self::cleanCredential($secret);
        if ($secret === '' || !self::secretStorageReady()) {
            return '';
        }

        try {
            return CryptoBox::protect($secret, self::encryptionKey());
        } catch (\Throwable $error) {
            return '';
        }
    }

    private static function unprotectSecret(string $stored): string
    {
        $stored = trim($stored);
        if ($stored === '' || !self::secretStorageReady()) {
            return '';
        }

        if (!CryptoBox::isV2($stored)) {
            return '';
        }

        return self::cleanCredential(CryptoBox::reveal($stored, self::encryptionKey()));
    }

    private static function protectAccessToken(string $accessToken): string
    {
        $accessToken = self::cleanAccessToken($accessToken);
        if ($accessToken === '' || !self::secretStorageReady()) {
            return '';
        }

        try {
            return CryptoBox::protect($accessToken, self::encryptionKey());
        } catch (\Throwable $error) {
            return '';
        }
    }

    private static function unprotectAccessToken(string $stored): string
    {
        $stored = trim($stored);
        if ($stored === '' || !self::secretStorageReady()) {
            return '';
        }

        if (!CryptoBox::isV2($stored)) {
            return '';
        }

        return self::cleanAccessToken(CryptoBox::reveal($stored, self::encryptionKey()));
    }

    private static function isProtectedSecret(string $stored): bool
    {
        return CryptoBox::isV2($stored);
    }

    private function accessTokenCacheKey(): string
    {
        $refreshToken = $this->refreshToken();
        if ($refreshToken === '') {
            return '';
        }

        $material = $refreshToken . '|' . $this->clientId();

        return self::ACCESS_TOKEN_TRANSIENT_PREFIX . substr(hash_hmac('sha256', $material, self::macKey()), 0, 40);
    }

    private static function encryptionKey(): string
    {
        return hash('sha256', (string) wp_salt('auth') . '|shieldx-google-drive-encryption', true);
    }

    private static function macKey(): string
    {
        return hash('sha256', (string) wp_salt('secure_auth') . '|shieldx-google-drive-mac');
    }

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