<?php

if (!defined('ABSPATH')) {
    exit;
}

class BS2FA_Trusted_Devices {
    const COOKIE_NAME = 'bs2fa_trusted_device';

    public static function enabled() {
        return (bool) BS2FA_Helpers::get_option('trusted_devices_enabled', 1);
    }

    public static function duration_days() {
        return max(1, (int) BS2FA_Helpers::get_option('trusted_device_days', 30));
    }

    protected static function meta_key() {
        return 'bs2fa_trusted_devices';
    }

    protected static function user_agent_hash() {
        $agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : 'unknown-agent';
        return BS2FA_Helpers::context_hash($agent);
    }

    protected static function current_cookie_value() {
        $raw = isset($_COOKIE[self::COOKIE_NAME]) ? sanitize_text_field(wp_unslash($_COOKIE[self::COOKIE_NAME])) : '';
        if (!$raw || strpos($raw, '.') === false) {
            return ['', ''];
        }

        [$selector, $validator] = array_pad(explode('.', $raw, 2), 2, '');
        return [sanitize_key($selector), preg_replace('/[^A-Za-z0-9\-_]/', '', (string) $validator)];
    }

    protected static function read_devices($user_id) {
        $devices = get_user_meta($user_id, self::meta_key(), true);
        return is_array($devices) ? array_values($devices) : [];
    }

    protected static function write_devices($user_id, $devices) {
        update_user_meta($user_id, self::meta_key(), array_values($devices));
    }

    public static function get_devices($user_id) {
        $devices = self::prune_expired($user_id);
        usort($devices, function ($a, $b) {
            return (int) ($b['added_at'] ?? 0) <=> (int) ($a['added_at'] ?? 0);
        });
        return $devices;
    }

    public static function prune_expired($user_id) {
        $devices = self::read_devices($user_id);
        $now = time();
        $filtered = array_values(array_filter($devices, function ($device) use ($now) {
            return !empty($device['expires_at']) && (int) $device['expires_at'] > $now;
        }));

        if (count($filtered) !== count($devices)) {
            self::write_devices($user_id, $filtered);
        }

        return $filtered;
    }

    public static function trust_requested_from_request() {
        if (!self::enabled()) {
            return false;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        return !empty($_POST['bs2fa_trust_device']);
    }

    public static function trust_label_from_request() {
        if (!self::enabled()) {
            return '';
        }

        // phpcs:disable WordPress.Security.NonceVerification.Missing
        if (empty($_POST['bs2fa_trust_device'])) {
            return '';
        }

        $raw_label = isset($_POST['bs2fa_trust_device_label']) ? $_POST['bs2fa_trust_device_label'] : '';
        $label = sanitize_text_field(wp_unslash($raw_label));
        // phpcs:enable WordPress.Security.NonceVerification.Missing

        return BS2FA_Helpers::sanitize_device_label($label, self::device_label());
    }

    public static function register_device($user_id, $label = '') {
        if (!self::enabled()) {
            return [];
        }

        $selector = strtolower(wp_generate_password(12, false, false));
        $validator = BS2FA_Helpers::random_token(32);
        $expires_at = time() + (self::duration_days() * DAY_IN_SECONDS);
        $device = [
            'selector' => $selector,
            'token_hash' => BS2FA_Helpers::context_hash($validator),
            'ua_hash' => self::user_agent_hash(),
            'label' => BS2FA_Helpers::sanitize_device_label($label, self::device_label()),
            'added_at' => time(),
            'last_used_at' => time(),
            'expires_at' => $expires_at,
        ];

        $devices = self::prune_expired($user_id);
        array_unshift($devices, $device);
        $devices = array_slice($devices, 0, 10);
        self::write_devices($user_id, $devices);
        BS2FA_Helpers::secure_cookie(self::COOKIE_NAME, $selector . '.' . $validator, $expires_at);

        return $device;
    }

    public static function update_labels($user_id, $labels) {
        $devices = self::read_devices($user_id);
        if (empty($devices) || !is_array($labels)) {
            return 0;
        }

        $changed = 0;
        foreach ($devices as $index => $device) {
            $selector = (string) ($device['selector'] ?? '');
            if ($selector === '' || !isset($labels[$selector])) {
                continue;
            }
            $new_label = BS2FA_Helpers::sanitize_device_label($labels[$selector], (string) ($device['label'] ?? self::device_label()));
            if ($new_label !== (string) ($device['label'] ?? '')) {
                $devices[$index]['label'] = $new_label;
                $changed++;
            }
        }

        if ($changed > 0) {
            self::write_devices($user_id, $devices);
        }

        return $changed;
    }

    public static function is_trusted_for_user($user_id) {
        if (!self::enabled()) {
            return false;
        }

        [$selector, $validator] = self::current_cookie_value();
        if (!$selector || !$validator) {
            return false;
        }

        $devices = self::prune_expired($user_id);
        $ua_hash = self::user_agent_hash();

        foreach ($devices as $index => $device) {
            if (($device['selector'] ?? '') !== $selector) {
                continue;
            }

            $valid = hash_equals((string) ($device['token_hash'] ?? ''), BS2FA_Helpers::context_hash($validator))
                && hash_equals((string) ($device['ua_hash'] ?? ''), $ua_hash)
                && (int) ($device['expires_at'] ?? 0) > time();

            if (!$valid) {
                self::revoke_device($user_id, $selector);
                return false;
            }

            $devices[$index]['last_used_at'] = time();
            self::write_devices($user_id, $devices);
            return true;
        }

        return false;
    }

    public static function revoke_current_device($user_id) {
        [$selector] = self::current_cookie_value();
        if ($selector) {
            self::revoke_device($user_id, $selector);
        }
        self::clear_cookie();
    }

    public static function revoke_device($user_id, $selector) {
        $selector = sanitize_key((string) $selector);
        if (!$selector) {
            return;
        }

        $devices = array_values(array_filter(self::read_devices($user_id), function ($device) use ($selector) {
            return ($device['selector'] ?? '') !== $selector;
        }));
        self::write_devices($user_id, $devices);

        [$cookie_selector] = self::current_cookie_value();
        if ($cookie_selector === $selector) {
            self::clear_cookie();
        }
    }

    public static function revoke_all($user_id) {
        delete_user_meta($user_id, self::meta_key());
        if (is_user_logged_in() && (int) get_current_user_id() === (int) $user_id) {
            self::clear_cookie();
        }
    }

    public static function clear_cookie() {
        BS2FA_Helpers::clear_cookie(self::COOKIE_NAME);
    }

    protected static function device_label() {
        $ua = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : 'Unknown device';
        $fragments = [];
        foreach (['Windows', 'Mac OS X', 'iPhone', 'iPad', 'Android', 'Linux'] as $needle) {
            if (stripos($ua, $needle) !== false) {
                $fragments[] = $needle;
                break;
            }
        }
        foreach (['Chrome', 'Firefox', 'Safari', 'Edge', 'Opera'] as $needle) {
            if (stripos($ua, $needle) !== false) {
                $fragments[] = $needle;
                break;
            }
        }
        if (empty($fragments)) {
            $label = __('Trusted device', 'brightery-secure-2fa');
        } else {
            $label = implode(' / ', $fragments);
        }

        return BS2FA_Helpers::sanitize_device_label($label, __('Trusted device', 'brightery-secure-2fa'));
    }
}