<?php
namespace WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Services;

use WPUltimateSecurity\UltimateSecurity\Support\Helpers\Utils;
use WPUltimateSecurity\UltimateSecurity\Support\Helpers\DateHelper;
use WPUltimateSecurity\UltimateSecurity\Support\Helpers\StringHelper;
use WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Helpers\DeviceFingerprint;
use WPUltimateSecurity\UltimateSecurity\Infrastructure\Persistence\Repositories\TrustedDeviceRepository;

/**
 * Trusted Device Service
 *
 * Manages trusted devices for 2FA bypass functionality.
 * Allows users to mark devices as trusted to skip 2FA verification
 * for a configured duration.
 *
 * Uses TrustedDeviceRepository for data access, implementing the Repository Pattern
 * for cleaner separation of concerns and better testability.
 */
class TrustedDeviceService
{
    /**
     * Cookie name for trusted device token
     */
    const COOKIE_NAME = 'ultimate_security_trusted_device';

    /**
     * Default trust duration in days
     */
    const DEFAULT_TRUST_DURATION = 30;

    /**
     * Default maximum devices per user
     */
    const DEFAULT_MAX_DEVICES = 5;

    /**
     * Repository instance (lazy-loaded singleton)
     *
     * @var TrustedDeviceRepository|null
     */
    private static ?TrustedDeviceRepository $repository = null;

    /**
     * Get the repository instance
     *
     * @return TrustedDeviceRepository
     */
    private static function getRepository(): TrustedDeviceRepository
    {
        if (self::$repository === null) {
            self::$repository = new TrustedDeviceRepository();
        }
        return self::$repository;
    }

    /**
     * Get the trusted devices table name
     *
     * @return string
     * @deprecated Use repository methods instead
     */
    private static function get_table_name()
    {
        global $wpdb;
        return $wpdb->prefix . 'ultimate_security_trusted_devices';
    }

    /**
     * Check if trusted devices feature is enabled
     *
     * @return bool
     */
    public static function is_enabled()
    {
        $options = Utils::ultimate_security_get_option();
        return isset($options['security']['2fa_authentication']['trusted_devices']['enabled'])
            && $options['security']['2fa_authentication']['trusted_devices']['enabled'] === '1';
    }

    /**
     * Get trusted devices settings
     *
     * @return array
     */
    public static function get_settings()
    {
        $options = Utils::ultimate_security_get_option();
        $defaults = [
            'enabled' => '0',
            'trust_duration' => '30',
            'max_devices' => '5',
            'notify_new_device' => '1',
            'show_remember_checkbox' => '1',
        ];

        return array_merge(
            $defaults,
            $options['security']['2fa_authentication']['trusted_devices'] ?? []
        );
    }

    /**
     * Check if the current device is trusted for a user
     *
     * @param int $user_id User ID
     * @return bool True if device is trusted and valid
     */
    public static function is_device_trusted($user_id)
    {
        if (!self::is_enabled()) {
            return false;
        }

        // Get the device token from cookie
        $device_token = self::get_device_token_from_cookie();
        if (empty($device_token)) {
            return false;
        }

        // Validate the token against database
        $device = self::get_device_by_token($device_token);
        if (!$device) {
            return false;
        }

        // Verify device belongs to this user
        if ((int) $device->user_id !== (int) $user_id) {
            return false;
        }

        // Check if device is revoked
        if ($device->is_revoked) {
            return false;
        }

        // Check if device has expired
        if (strtotime($device->expires_at) < time()) {
            return false;
        }

        // Verify fingerprint matches current device
        $current_fingerprint = DeviceFingerprint::generate();
        if ($device->device_fingerprint !== $current_fingerprint) {
            return false;
        }

        // Update last used timestamp
        self::update_last_used($device->id);

        return true;
    }

    /**
     * Trust the current device for a user
     *
     * @param int $user_id User ID
     * @param array $device_info Optional device information from client
     * @return array|false Device data on success, false on failure
     */
    public static function trust_device($user_id, $device_info = [])
    {
        if (!self::is_enabled()) {
            return false;
        }

        $settings = self::get_settings();
        $max_devices = (int) ($settings['max_devices'] ?? self::DEFAULT_MAX_DEVICES);

        // Check if user has reached max devices
        $current_devices = self::get_user_devices($user_id);
        if (count($current_devices) >= $max_devices) {
            // Remove oldest device
            self::remove_oldest_device($user_id);
        }

        // Generate device fingerprint and token
        $fingerprint = DeviceFingerprint::generate();
        $device_token = StringHelper::random(64, 'hex');

        // Parse user agent for device details
        $user_agent = DeviceFingerprint::get_user_agent();
        $parsed_ua = self::parse_user_agent($user_agent);

        // Calculate expiration
        $trust_duration = (int) ($settings['trust_duration'] ?? self::DEFAULT_TRUST_DURATION);
        $expires_at = DateHelper::add(time(), "{$trust_duration} days");

        // Build device name
        $device_name = self::build_device_name($parsed_ua, $device_info);

        // Insert into database
        global $wpdb;
        $table_name = self::get_table_name();

        $result = $wpdb->insert(
            $table_name,
            [
                'user_id' => $user_id,
                'device_fingerprint' => $fingerprint,
                'device_token' => $device_token,
                'device_name' => $device_name,
                'browser' => $parsed_ua['browser'],
                'browser_version' => $parsed_ua['browser_version'],
                'os' => $parsed_ua['os'],
                'os_version' => $parsed_ua['os_version'],
                'device_type' => $parsed_ua['device_type'],
                'ip_address' => DeviceFingerprint::get_client_ip(),
                'location' => $device_info['location'] ?? null,
                'last_used_at' => DateHelper::now(),
                'expires_at' => $expires_at,
                'is_revoked' => 0,
            ],
            ['%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d']
        );

        if (!$result) {
            return false;
        }

        $device_id = $wpdb->insert_id;

        // Set the cookie
        self::set_device_cookie($device_token, $trust_duration);

        // Send new device notification if enabled
        if ($settings['notify_new_device'] === '1') {
            self::send_new_device_notification($user_id, $device_name);
        }

        return [
            'id' => $device_id,
            'device_token' => $device_token,
            'device_name' => $device_name,
            'expires_at' => $expires_at,
        ];
    }

    /**
     * Get device by token
     *
     * @param string $token Device token
     * @return object|null Device object or null
     */
    public static function get_device_by_token($token)
    {
        return self::getRepository()->findByToken($token);
    }

    /**
     * Get all devices for a user
     *
     * @param int $user_id User ID
     * @param bool $include_revoked Include revoked devices
     * @return array List of devices
     */
    public static function get_user_devices($user_id, $include_revoked = false)
    {
        if ($include_revoked) {
            return self::getRepository()->findByUserId($user_id);
        }
        return self::getRepository()->findActiveByUserId($user_id);
    }

    /**
     * Get all trusted devices (admin view)
     *
     * @param array $args Query arguments
     * @return array List of devices with user info
     */
    public static function get_all_devices($args = [])
    {
        global $wpdb;
        $table_name = self::get_table_name();

        $defaults = [
            'page' => 1,
            'per_page' => 20,
            'orderby' => 'created_at',
            'order' => 'DESC',
            'user_id' => null,
            'include_revoked' => false,
            'include_expired' => false,
        ];

        $args = wp_parse_args($args, $defaults);
        $offset = ($args['page'] - 1) * $args['per_page'];

        $where_clauses = ['1=1'];
        $where_values = [];

        if ($args['user_id']) {
            $where_clauses[] = 'td.user_id = %d';
            $where_values[] = $args['user_id'];
        }

        if (!$args['include_revoked']) {
            $where_clauses[] = 'td.is_revoked = 0';
        }

        if (!$args['include_expired']) {
            $where_clauses[] = 'td.expires_at > NOW()';
        }

        $where_sql = implode(' AND ', $where_clauses);
        $orderby = sanitize_sql_orderby($args['orderby'] . ' ' . $args['order']) ?: 'td.created_at DESC';

        $query = "SELECT td.*, u.user_login, u.user_email, u.display_name
                  FROM {$table_name} td
                  LEFT JOIN {$wpdb->users} u ON td.user_id = u.ID
                  WHERE {$where_sql}
                  ORDER BY {$orderby}
                  LIMIT %d OFFSET %d";

        $where_values[] = $args['per_page'];
        $where_values[] = $offset;

        $devices = $wpdb->get_results(
            $wpdb->prepare($query, ...$where_values)
        );

        // Get total count
        $count_query = "SELECT COUNT(*) FROM {$table_name} td WHERE {$where_sql}";
        array_pop($where_values); // Remove offset
        array_pop($where_values); // Remove limit
        $total = $wpdb->get_var(
            empty($where_values) ? $count_query : $wpdb->prepare($count_query, ...$where_values)
        );

        return [
            'devices' => $devices,
            'total' => (int) $total,
            'pages' => ceil($total / $args['per_page']),
        ];
    }

    /**
     * Revoke a trusted device
     *
     * @param int $device_id Device ID
     * @param int $user_id User ID (for verification)
     * @return bool Success
     */
    public static function revoke_device($device_id, $user_id = null)
    {
        global $wpdb;
        $table_name = self::get_table_name();

        $where = ['id' => $device_id];
        $where_format = ['%d'];

        // If user_id provided, verify ownership
        if ($user_id !== null) {
            $where['user_id'] = $user_id;
            $where_format[] = '%d';
        }

        $result = $wpdb->update(
            $table_name,
            ['is_revoked' => 1],
            $where,
            ['%d'],
            $where_format
        );

        return $result !== false;
    }

    /**
     * Revoke all devices for a user
     *
     * @param int $user_id User ID
     * @return int Number of devices revoked
     */
    public static function revoke_all_user_devices($user_id)
    {
        global $wpdb;
        $table_name = self::get_table_name();

        return $wpdb->update(
            $table_name,
            ['is_revoked' => 1],
            ['user_id' => $user_id, 'is_revoked' => 0],
            ['%d'],
            ['%d', '%d']
        );
    }

    /**
     * Delete a trusted device permanently
     *
     * @param int $device_id Device ID
     * @param int $user_id User ID (for verification)
     * @return bool Success
     */
    public static function delete_device($device_id, $user_id = null)
    {
        global $wpdb;
        $table_name = self::get_table_name();

        $where = ['id' => $device_id];
        $where_format = ['%d'];

        if ($user_id !== null) {
            $where['user_id'] = $user_id;
            $where_format[] = '%d';
        }

        return $wpdb->delete($table_name, $where, $where_format) !== false;
    }

    /**
     * Clean up expired and revoked devices
     *
     * @param int $days_old Delete devices older than this (for revoked)
     * @return int Number of devices deleted
     */
    public static function cleanup_expired_devices($days_old = 30)
    {
        global $wpdb;
        $table_name = self::get_table_name();

        // Delete expired devices
        $expired_deleted = $wpdb->query(
            "DELETE FROM {$table_name} WHERE expires_at < NOW()"
        );

        // Delete revoked devices older than X days
        $revoked_deleted = $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$table_name} WHERE is_revoked = 1 AND updated_at < DATE_SUB(NOW(), INTERVAL %d DAY)",
                $days_old
            )
        );

        return (int) $expired_deleted + (int) $revoked_deleted;
    }

    /**
     * Get device statistics
     *
     * @return array Statistics
     */
    public static function get_statistics()
    {
        global $wpdb;
        $table_name = self::get_table_name();

        $stats = $wpdb->get_row(
            "SELECT
                COUNT(*) as total_devices,
                COUNT(DISTINCT user_id) as unique_users,
                SUM(CASE WHEN is_revoked = 0 AND expires_at > NOW() THEN 1 ELSE 0 END) as active_devices,
                SUM(CASE WHEN is_revoked = 1 THEN 1 ELSE 0 END) as revoked_devices,
                SUM(CASE WHEN expires_at <= NOW() AND is_revoked = 0 THEN 1 ELSE 0 END) as expired_devices
             FROM {$table_name}"
        );

        return [
            'total_devices' => (int) ($stats->total_devices ?? 0),
            'unique_users' => (int) ($stats->unique_users ?? 0),
            'active_devices' => (int) ($stats->active_devices ?? 0),
            'revoked_devices' => (int) ($stats->revoked_devices ?? 0),
            'expired_devices' => (int) ($stats->expired_devices ?? 0),
        ];
    }

    /**
     * Update last used timestamp for a device
     *
     * @param int $device_id Device ID
     * @return bool Success
     */
    private static function update_last_used($device_id)
    {
        global $wpdb;
        $table_name = self::get_table_name();

        return $wpdb->update(
            $table_name,
            [
                'last_used_at' => DateHelper::now(),
                'ip_address' => DeviceFingerprint::get_client_ip(),
            ],
            ['id' => $device_id],
            ['%s', '%s'],
            ['%d']
        ) !== false;
    }

    /**
     * Remove the oldest device for a user
     *
     * @param int $user_id User ID
     * @return bool Success
     */
    private static function remove_oldest_device($user_id)
    {
        global $wpdb;
        $table_name = self::get_table_name();

        // Find oldest device
        $oldest = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT id FROM {$table_name} WHERE user_id = %d AND is_revoked = 0 ORDER BY last_used_at ASC LIMIT 1",
                $user_id
            )
        );

        if ($oldest) {
            return self::delete_device($oldest);
        }

        return false;
    }

    /**
     * Get device token from cookie
     *
     * @return string|null Device token or null
     */
    private static function get_device_token_from_cookie()
    {
        if (!isset($_COOKIE[self::COOKIE_NAME])) {
            return null;
        }

        return sanitize_text_field(wp_unslash($_COOKIE[self::COOKIE_NAME]));
    }

    /**
     * Set the trusted device cookie
     *
     * @param string $token Device token
     * @param int $days Duration in days
     */
    private static function set_device_cookie($token, $days)
    {
        $expires = time() + ($days * DAY_IN_SECONDS);
        $secure = is_ssl();
        $path = COOKIEPATH;
        $domain = COOKIE_DOMAIN;

        setcookie(
            self::COOKIE_NAME,
            $token,
            [
                'expires' => $expires,
                'path' => $path,
                'domain' => $domain,
                'secure' => $secure,
                'httponly' => true,
                'samesite' => 'Strict',
            ]
        );
    }

    /**
     * Clear the trusted device cookie
     */
    public static function clear_device_cookie()
    {
        setcookie(
            self::COOKIE_NAME,
            '',
            [
                'expires' => time() - YEAR_IN_SECONDS,
                'path' => COOKIEPATH,
                'domain' => COOKIE_DOMAIN,
                'secure' => is_ssl(),
                'httponly' => true,
                'samesite' => 'Strict',
            ]
        );
    }

    /**
     * Parse user agent string
     *
     * @param string $user_agent User agent string
     * @return array Parsed information
     */
    private static function parse_user_agent($user_agent)
    {
        $result = [
            'browser' => 'Unknown',
            'browser_version' => '',
            'os' => 'Unknown',
            'os_version' => '',
            'device_type' => 'desktop',
        ];

        // Detect device type
        if (preg_match('/mobile|android|iphone|ipod|blackberry|opera mini|iemobile/i', $user_agent)) {
            $result['device_type'] = 'mobile';
        } elseif (preg_match('/tablet|ipad|playbook|silk/i', $user_agent)) {
            $result['device_type'] = 'tablet';
        }

        // Detect OS
        $os_patterns = [
            '/windows nt 10/i' => ['Windows', '10'],
            '/windows nt 6\.3/i' => ['Windows', '8.1'],
            '/windows nt 6\.2/i' => ['Windows', '8'],
            '/windows nt 6\.1/i' => ['Windows', '7'],
            '/macintosh|mac os x/i' => ['macOS', ''],
            '/linux/i' => ['Linux', ''],
            '/ubuntu/i' => ['Ubuntu', ''],
            '/iphone os (\d+)/i' => ['iOS', '$1'],
            '/ipad.*os (\d+)/i' => ['iPadOS', '$1'],
            '/android (\d+)/i' => ['Android', '$1'],
            '/chrome os/i' => ['Chrome OS', ''],
        ];

        foreach ($os_patterns as $pattern => $info) {
            if (preg_match($pattern, $user_agent, $matches)) {
                $result['os'] = $info[0];
                if (isset($matches[1])) {
                    $result['os_version'] = $matches[1];
                } elseif ($info[1]) {
                    $result['os_version'] = $info[1];
                }
                break;
            }
        }

        // Detect browser
        $browser_patterns = [
            '/edg\/(\d+)/i' => ['Edge', '$1'],
            '/edge\/(\d+)/i' => ['Edge', '$1'],
            '/opr\/(\d+)/i' => ['Opera', '$1'],
            '/opera\/(\d+)/i' => ['Opera', '$1'],
            '/chrome\/(\d+)/i' => ['Chrome', '$1'],
            '/safari\/(\d+)/i' => ['Safari', '$1'],
            '/firefox\/(\d+)/i' => ['Firefox', '$1'],
            '/msie (\d+)/i' => ['Internet Explorer', '$1'],
            '/trident.*rv:(\d+)/i' => ['Internet Explorer', '$1'],
        ];

        foreach ($browser_patterns as $pattern => $info) {
            if (preg_match($pattern, $user_agent, $matches)) {
                $result['browser'] = $info[0];
                if (isset($matches[1])) {
                    $result['browser_version'] = $matches[1];
                }
                break;
            }
        }

        return $result;
    }

    /**
     * Build a human-readable device name
     *
     * @param array $parsed_ua Parsed user agent info
     * @param array $device_info Additional device info
     * @return string Device name
     */
    private static function build_device_name($parsed_ua, $device_info = [])
    {
        // Use custom name if provided
        if (!empty($device_info['device_name'])) {
            return sanitize_text_field($device_info['device_name']);
        }

        $parts = [];

        // Add browser
        if ($parsed_ua['browser'] !== 'Unknown') {
            $browser = $parsed_ua['browser'];
            if ($parsed_ua['browser_version']) {
                $browser .= ' ' . $parsed_ua['browser_version'];
            }
            $parts[] = $browser;
        }

        // Add OS
        if ($parsed_ua['os'] !== 'Unknown') {
            $os = $parsed_ua['os'];
            if ($parsed_ua['os_version']) {
                $os .= ' ' . $parsed_ua['os_version'];
            }
            $parts[] = $os;
        }

        // Add device type indicator
        if ($parsed_ua['device_type'] !== 'desktop') {
            $parts[] = '(' . ucfirst($parsed_ua['device_type']) . ')';
        }

        return empty($parts) ? __('Unknown Device', 'ultimate-security') : implode(' on ', $parts);
    }

    /**
     * Send notification about new trusted device
     *
     * @param int $user_id User ID
     * @param string $device_name Device name
     * @return bool Success
     */
    private static function send_new_device_notification($user_id, $device_name)
    {
        $user = get_userdata($user_id);
        if (!$user) {
            return false;
        }

        $site_name = get_bloginfo('name');
        $ip_address = DeviceFingerprint::get_client_ip();

        $subject = sprintf(
            /* translators: %s: Site name */
            __('[%s] New Trusted Device Added', 'ultimate-security'),
            $site_name
        );

        $message = sprintf(
            /* translators: 1: User display name, 2: Device name, 3: IP address, 4: Site name, 5: Date/time */
            __(
                "Hello %1\$s,\n\nA new device has been marked as trusted for your account:\n\nDevice: %2\$s\nIP Address: %3\$s\nDate: %5\$s\n\nIf this wasn't you, please log in to %4\$s and revoke this device immediately, then change your password.\n\nBest regards,\nThe %4\$s Team",
                'ultimate-security'
            ),
            $user->display_name,
            $device_name,
            $ip_address,
            $site_name,
            DateHelper::format(time())
        );

        $headers = ['Content-Type: text/plain; charset=UTF-8'];

        return wp_mail($user->user_email, $subject, $message, $headers);
    }

    /**
     * Get count of trusted devices for a user
     *
     * @param int $user_id User ID
     * @return int Count
     */
    public static function get_user_device_count($user_id)
    {
        global $wpdb;
        $table_name = self::get_table_name();

        return (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$table_name} WHERE user_id = %d AND is_revoked = 0 AND expires_at > NOW()",
                $user_id
            )
        );
    }

    /**
     * Check if showing remember checkbox is enabled
     *
     * @return bool
     */
    public static function show_remember_checkbox()
    {
        if (!self::is_enabled()) {
            return false;
        }

        $settings = self::get_settings();
        return $settings['show_remember_checkbox'] === '1';
    }
}
