<?php
/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

/**
 * Authentication Endpoints Unit Tests
 *
 * Comprehensive test suite for Care_API\Endpoints\Auth_Endpoints class
 * Tests authentication workflows, token management, user authorization, and security
 *
 * @package Care_API\Tests\Unit\Endpoints
 * @version    1.0.0
 * @author     Descomplicar® <dev@descomplicar.pt>
 * @link       https://descomplicar.pt
 * @since      1.0.0
 */

namespace Care_API\Tests\Unit\Endpoints;

use Care_API\Endpoints\Auth_Endpoints;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use WP_User;

/**
 * Class AuthEndpointsTest
 *
 * Unit tests for Auth_Endpoints class covering:
 * - Login/logout functionality
 * - Token management and validation
 * - User profile operations
 * - Permission and authorization checks
 * - Rate limiting and security measures
 *
 * @since 1.0.0
 */
class AuthEndpointsTest extends \Care_API_Test_Case {

    /**
     * Auth_Endpoints instance for testing
     *
     * @var Auth_Endpoints
     */
    private $auth_endpoints;

    /**
     * Test users for different scenarios
     *
     * @var array
     */
    private $test_users = array();

    /**
     * Test setup before each test method
     *
     * @since 1.0.0
     */
    public function setUp(): void {
        parent::setUp();

        // Create test users with specific roles
        $this->test_users = array(
            'admin' => $this->create_test_user('administrator'),
            'doctor' => $this->create_test_user('kivicare_doctor'),
            'patient' => $this->create_test_user('kivicare_patient'),
            'receptionist' => $this->create_test_user('kivicare_receptionist'),
            'subscriber' => $this->create_test_user('subscriber') // No API access
        );

        // Mock WordPress functions for testing
        $this->mock_wordpress_functions();

        // Register auth endpoints
        Auth_Endpoints::register_routes();
    }

    /**
     * Test 1: Authentication Route Registration
     *
     * Verifies that all authentication routes are properly registered:
     * - Login endpoint with proper methods and validation
     * - Logout endpoint with authentication requirement
     * - Token refresh and validation endpoints
     * - Profile management endpoints
     * - Password reset endpoints
     *
     * @test
     * @since 1.0.0
     */
    public function test_authentication_route_registration() {
        // Get all registered routes
        $routes = $this->server->get_routes();

        // Define expected authentication endpoints
        $expected_auth_routes = array(
            '/care/v1/auth/login' => array('POST'),
            '/care/v1/auth/logout' => array('POST'),
            '/care/v1/auth/refresh' => array('POST'),
            '/care/v1/auth/validate' => array('GET'),
            '/care/v1/auth/profile' => array('GET', 'PUT'),
            '/care/v1/auth/forgot-password' => array('POST'),
            '/care/v1/auth/reset-password' => array('POST'),
        );

        foreach ($expected_auth_routes as $route => $methods) {
            $this->assertArrayHasKey(
                $route,
                $routes,
                "Route {$route} should be registered"
            );

            $route_config = $routes[$route];
            $registered_methods = array();

            // Extract registered methods from route configuration
            foreach ($route_config as $handler) {
                if (isset($handler['methods'])) {
                    $handler_methods = (array) $handler['methods'];
                    $registered_methods = array_merge($registered_methods, $handler_methods);
                }
            }

            foreach ($methods as $method) {
                $this->assertContains(
                    $method,
                    $registered_methods,
                    "Route {$route} should support {$method} method"
                );
            }
        }

        // Test route callbacks are properly set
        $login_route = $routes['/care/v1/auth/login'][0];
        $this->assertArrayHasKey('callback', $login_route, 'Login route should have callback');
        $this->assertArrayHasKey('permission_callback', $login_route, 'Login route should have permission callback');
        $this->assertArrayHasKey('args', $login_route, 'Login route should have argument validation');

        // Test login route arguments
        $login_args = $login_route['args'];
        $this->assertArrayHasKey('username', $login_args, 'Login should require username parameter');
        $this->assertArrayHasKey('password', $login_args, 'Login should require password parameter');

        $this->assertTrue(
            $login_args['username']['required'],
            'Username should be required parameter'
        );

        $this->assertTrue(
            $login_args['password']['required'],
            'Password should be required parameter'
        );
    }

    /**
     * Test 2: Login Functionality and Validation
     *
     * Tests complete login workflow:
     * - Valid credential authentication
     * - Invalid credential rejection
     * - User permission validation
     * - Rate limiting enforcement
     * - Response data structure
     *
     * @test
     * @since 1.0.0
     */
    public function test_login_functionality_and_validation() {
        // Test valid login with authorized user (admin)
        $admin_user = get_userdata($this->test_users['admin']);

        // Mock wp_authenticate to return valid user
        add_filter('authenticate', function($user, $username, $password) use ($admin_user) {
            if ($username === $admin_user->user_login && $password === 'valid_password') {
                return $admin_user;
            }
            return new WP_Error('invalid_credentials', 'Invalid credentials');
        }, 10, 3);

        $login_request = new WP_REST_Request('POST', '/care/v1/auth/login');
        $login_request->set_body_params(array(
            'username' => $admin_user->user_login,
            'password' => 'valid_password'
        ));

        $login_response = $this->server->dispatch($login_request);

        // Test successful login response structure
        if ($login_response->get_status() === 200) {
            $response_data = $login_response->get_data();

            $this->assertArrayHasKey('success', $response_data);
            $this->assertTrue($response_data['success']);

            $this->assertArrayHasKey('data', $response_data);
            $this->assertArrayHasKey('user', $response_data['data']);

            $user_data = $response_data['data']['user'];
            $this->assertArrayHasKey('id', $user_data);
            $this->assertArrayHasKey('username', $user_data);
            $this->assertArrayHasKey('email', $user_data);
            $this->assertArrayHasKey('roles', $user_data);
            $this->assertArrayHasKey('capabilities', $user_data);
        }

        // Test invalid credentials
        $invalid_login_request = new WP_REST_Request('POST', '/care/v1/auth/login');
        $invalid_login_request->set_body_params(array(
            'username' => 'invalid_user',
            'password' => 'invalid_password'
        ));

        $invalid_response = $this->server->dispatch($invalid_login_request);
        $this->assertEquals(
            401,
            $invalid_response->get_status(),
            'Invalid credentials should return 401 status'
        );

        // Test missing parameters
        $missing_params_request = new WP_REST_Request('POST', '/care/v1/auth/login');
        $missing_params_request->set_body_params(array(
            'username' => 'testuser'
            // Missing password
        ));

        $missing_response = $this->server->dispatch($missing_params_request);
        $this->assertEquals(
            400,
            $missing_response->get_status(),
            'Missing parameters should return 400 status'
        );

        // Test username validation
        $this->assertTrue(
            Auth_Endpoints::validate_username('valid_user'),
            'Valid username should pass validation'
        );

        $this->assertTrue(
            Auth_Endpoints::validate_username('user@example.com'),
            'Valid email should pass username validation'
        );

        $this->assertFalse(
            Auth_Endpoints::validate_username(''),
            'Empty username should fail validation'
        );

        // Test password validation
        $this->assertTrue(
            Auth_Endpoints::validate_password('validpassword123'),
            'Valid password should pass validation'
        );

        $this->assertFalse(
            Auth_Endpoints::validate_password('short'),
            'Short password should fail validation'
        );

        $this->assertFalse(
            Auth_Endpoints::validate_password(''),
            'Empty password should fail validation'
        );

        // Clean up filter
        remove_all_filters('authenticate');
    }

    /**
     * Test 3: User Authorization and Permissions
     *
     * Validates user permission system:
     * - Role-based API access control
     * - Capability-based endpoint access
     * - User status validation (active/suspended)
     * - API capability assignment per role
     *
     * @test
     * @since 1.0.0
     */
    public function test_user_authorization_and_permissions() {
        // Test different user roles and their API access
        $role_access_tests = array(
            'administrator' => true,
            'kivicare_doctor' => true,
            'kivicare_patient' => true,
            'kivicare_receptionist' => true,
            'subscriber' => false // Should not have API access
        );

        foreach ($role_access_tests as $role => $should_have_access) {
            $user_id = $this->test_users[str_replace('kivicare_', '', $role)] ?? $this->test_users['subscriber'];
            $user = get_userdata($user_id);

            // Mock user_can_access_api method behavior
            $reflection = new \ReflectionClass(Auth_Endpoints::class);
            $method = $reflection->getMethod('user_can_access_api');
            $method->setAccessible(true);

            $has_access = $method->invokeArgs(null, array($user));

            if ($should_have_access) {
                $this->assertTrue(
                    $has_access,
                    "User with role {$role} should have API access"
                );
            } else {
                $this->assertFalse(
                    $has_access,
                    "User with role {$role} should not have API access"
                );
            }
        }

        // Test user capability assignment
        $admin_user = get_userdata($this->test_users['admin']);
        $doctor_user = get_userdata($this->test_users['doctor']);
        $patient_user = get_userdata($this->test_users['patient']);

        // Mock get_user_api_capabilities method
        $reflection = new \ReflectionClass(Auth_Endpoints::class);
        $method = $reflection->getMethod('get_user_api_capabilities');
        $method->setAccessible(true);

        // Test admin capabilities
        $admin_caps = $method->invokeArgs(null, array($admin_user));
        $this->assertIsArray($admin_caps, 'Admin capabilities should be array');
        $this->assertContains('read_clinics', $admin_caps, 'Admin should have read_clinics capability');
        $this->assertContains('create_clinics', $admin_caps, 'Admin should have create_clinics capability');
        $this->assertContains('manage_settings', $admin_caps, 'Admin should have manage_settings capability');

        // Test doctor capabilities
        $doctor_caps = $method->invokeArgs(null, array($doctor_user));
        $this->assertIsArray($doctor_caps, 'Doctor capabilities should be array');
        $this->assertContains('read_patients', $doctor_caps, 'Doctor should have read_patients capability');
        $this->assertContains('create_encounters', $doctor_caps, 'Doctor should have create_encounters capability');
        $this->assertNotContains('delete_clinics', $doctor_caps, 'Doctor should not have delete_clinics capability');

        // Test patient capabilities (more limited)
        $patient_caps = $method->invokeArgs(null, array($patient_user));
        $this->assertIsArray($patient_caps, 'Patient capabilities should be array');
        $this->assertContains('read_appointments', $patient_caps, 'Patient should have read_appointments capability');
        $this->assertNotContains('create_patients', $patient_caps, 'Patient should not have create_patients capability');

        // Test user status validation
        $active_user = get_userdata($this->test_users['admin']);

        // Mock is_user_active method
        $is_active_method = $reflection->getMethod('is_user_active');
        $is_active_method->setAccessible(true);

        $this->assertTrue(
            $is_active_method->invokeArgs(null, array($active_user)),
            'User without status meta should be considered active'
        );

        // Test suspended user
        update_user_meta($this->test_users['admin'], 'account_status', 'suspended');
        $this->assertFalse(
            $is_active_method->invokeArgs(null, array($active_user)),
            'User with suspended status should not be active'
        );

        // Clean up
        delete_user_meta($this->test_users['admin'], 'account_status');
    }

    /**
     * Test 4: Profile Management Operations
     *
     * Tests user profile endpoints:
     * - Profile data retrieval
     * - Profile update operations
     * - Data validation and sanitization
     * - Permission checks for profile access
     *
     * @test
     * @since 1.0.0
     */
    public function test_profile_management_operations() {
        // Set current user for profile operations
        wp_set_current_user($this->test_users['admin']);

        // Test profile retrieval
        $profile_request = new WP_REST_Request('GET', '/care/v1/auth/profile');
        $profile_response = $this->server->dispatch($profile_request);

        if ($profile_response->get_status() === 200) {
            $profile_data = $profile_response->get_data();

            $this->assertArrayHasKey('success', $profile_data);
            $this->assertTrue($profile_data['success']);

            $this->assertArrayHasKey('data', $profile_data);
            $user_data = $profile_data['data'];

            // Test required profile fields
            $required_fields = array('id', 'username', 'email', 'first_name', 'last_name', 'roles');
            foreach ($required_fields as $field) {
                $this->assertArrayHasKey(
                    $field,
                    $user_data,
                    "Profile data should contain {$field} field"
                );
            }

            // Test profile meta fields
            $this->assertArrayHasKey('profile', $user_data, 'Should contain profile meta data');
        }

        // Test profile update
        $update_request = new WP_REST_Request('PUT', '/care/v1/auth/profile');
        $update_request->set_body_params(array(
            'first_name' => 'Updated First',
            'last_name' => 'Updated Last',
            'email' => 'updated@example.com'
        ));

        $update_response = $this->server->dispatch($update_request);

        if ($update_response->get_status() === 200) {
            $update_data = $update_response->get_data();
            $this->assertArrayHasKey('success', $update_data);
            $this->assertTrue($update_data['success']);
        }

        // Test profile update with invalid data
        $invalid_update_request = new WP_REST_Request('PUT', '/care/v1/auth/profile');
        $invalid_update_request->set_body_params(array(
            'email' => 'invalid-email-format'
        ));

        $invalid_update_response = $this->server->dispatch($invalid_update_request);
        $this->assertGreaterThanOrEqual(
            400,
            $invalid_update_response->get_status(),
            'Invalid email should return error status'
        );

        // Test profile access without authentication
        wp_set_current_user(0);
        $unauth_profile_request = new WP_REST_Request('GET', '/care/v1/auth/profile');
        $unauth_response = $this->server->dispatch($unauth_profile_request);

        $this->assertEquals(
            401,
            $unauth_response->get_status(),
            'Unauthenticated profile access should return 401'
        );
    }

    /**
     * Test 5: Rate Limiting and Security Measures
     *
     * Validates security implementations:
     * - Rate limiting for authentication attempts
     * - Token management and validation
     * - Password reset security
     * - Request logging and monitoring
     * - IP-based restrictions
     *
     * @test
     * @since 1.0.0
     */
    public function test_rate_limiting_and_security_measures() {
        // Test rate limiting for login attempts
        $rate_limit_result = Auth_Endpoints::check_rate_limit();
        $this->assertTrue(
            is_bool($rate_limit_result) || is_wp_error($rate_limit_result),
            'Rate limit check should return boolean or WP_Error'
        );

        // Simulate multiple failed login attempts to test rate limiting
        $client_ip = '192.168.1.100';
        $_SERVER['REMOTE_ADDR'] = $client_ip;

        // Mock transient functions for rate limiting
        $rate_limit_key = 'auth_rate_limit_' . md5($client_ip);
        set_transient($rate_limit_key, 5, 900); // Set to limit

        $rate_limited_result = Auth_Endpoints::check_rate_limit();
        $this->assertInstanceOf(
            'WP_Error',
            $rate_limited_result,
            'Rate limit should return WP_Error when exceeded'
        );

        if (is_wp_error($rate_limited_result)) {
            $this->assertEquals(
                'rate_limit_exceeded',
                $rate_limited_result->get_error_code(),
                'Rate limit error should have correct error code'
            );
        }

        // Clean up transient
        delete_transient($rate_limit_key);

        // Test password reset security
        $forgot_password_request = new WP_REST_Request('POST', '/care/v1/auth/forgot-password');
        $forgot_password_request->set_body_params(array(
            'username' => 'nonexistent@example.com'
        ));

        $forgot_response = $this->server->dispatch($forgot_password_request);

        // Should return success even for non-existent users (security measure)
        if ($forgot_response->get_status() === 200) {
            $forgot_data = $forgot_response->get_data();
            $this->assertArrayHasKey('success', $forgot_data);
            $this->assertTrue($forgot_data['success']);
            $this->assertStringContainsString(
                'If the user exists',
                $forgot_data['message'],
                'Should not reveal whether user exists'
            );
        }

        // Test token extraction from request
        $test_request = new WP_REST_Request('GET', '/care/v1/auth/validate');
        $test_request->set_header('authorization', 'Bearer test-jwt-token-here');

        // Mock get_token_from_request method
        $reflection = new \ReflectionClass(Auth_Endpoints::class);
        $method = $reflection->getMethod('get_token_from_request');
        $method->setAccessible(true);

        $extracted_token = $method->invokeArgs(null, array($test_request));
        $this->assertEquals(
            'test-jwt-token-here',
            $extracted_token,
            'Should correctly extract JWT token from Authorization header'
        );

        // Test client IP detection
        $ip_method = $reflection->getMethod('get_client_ip');
        $ip_method->setAccessible(true);

        // Test with various IP headers
        $_SERVER['HTTP_CF_CONNECTING_IP'] = '203.0.113.1';
        $detected_ip = $ip_method->invoke(null);
        $this->assertEquals(
            '203.0.113.1',
            $detected_ip,
            'Should detect IP from Cloudflare header'
        );

        // Test fallback to REMOTE_ADDR
        unset($_SERVER['HTTP_CF_CONNECTING_IP']);
        $_SERVER['REMOTE_ADDR'] = '192.168.1.50';
        $fallback_ip = $ip_method->invoke(null);
        $this->assertEquals(
            '192.168.1.50',
            $fallback_ip,
            'Should fallback to REMOTE_ADDR when no proxy headers'
        );

        // Test password reset validation
        $reset_request = new WP_REST_Request('POST', '/care/v1/auth/reset-password');
        $reset_request->set_body_params(array(
            'key' => 'invalid-key',
            'login' => 'testuser',
            'password' => 'newpassword123'
        ));

        $reset_response = $this->server->dispatch($reset_request);
        $this->assertGreaterThanOrEqual(
            400,
            $reset_response->get_status(),
            'Invalid reset key should return error status'
        );
    }

    /**
     * Test teardown after each test method
     *
     * @since 1.0.0
     */
    public function tearDown(): void {
        // Clear current user
        wp_set_current_user(0);

        // Clear any transients set during testing
        global $wpdb;
        $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_auth_rate_limit_%'");
        $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_auth_rate_limit_%'");

        // Clean up server variables
        unset($_SERVER['HTTP_CF_CONNECTING_IP']);
        unset($_SERVER['HTTP_X_FORWARDED_FOR']);
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';

        parent::tearDown();
    }

    /**
     * Helper method to create test user with specific role
     *
     * @param string $role User role
     * @return int User ID
     * @since 1.0.0
     */
    private function create_test_user($role) {
        return $this->factory->user->create(array(
            'user_login' => 'test_' . $role . '_' . wp_rand(1000, 9999),
            'user_email' => 'test' . wp_rand(1000, 9999) . '@example.com',
            'user_pass' => 'test_password_123',
            'first_name' => 'Test',
            'last_name' => 'User',
            'role' => $role
        ));
    }

    /**
     * Mock WordPress functions needed for testing
     *
     * @since 1.0.0
     */
    private function mock_wordpress_functions() {
        // Mock password reset functions if they don't exist
        if (!function_exists('get_password_reset_key')) {
            function get_password_reset_key($user) {
                return 'mock_reset_key_' . $user->ID . '_' . time();
            }
        }

        if (!function_exists('check_password_reset_key')) {
            function check_password_reset_key($key, $login) {
                if (strpos($key, 'mock_reset_key_') === 0) {
                    return get_user_by('login', $login);
                }
                return new \WP_Error('invalid_key', 'Invalid key');
            }
        }

        // Mock wp_mail function
        if (!function_exists('wp_mail')) {
            function wp_mail($to, $subject, $message, $headers = '', $attachments = array()) {
                return true; // Always succeed for testing
            }
        }

        // Mock sanitization functions if needed
        if (!function_exists('sanitize_user')) {
            function sanitize_user($username, $strict = false) {
                return strip_tags($username);
            }
        }

        if (!function_exists('validate_username')) {
            function validate_username($username) {
                return !empty($username) && strlen($username) >= 3;
            }
        }
    }
}