<?php
/**
 * Doctor Model Unit Tests
 *
 * Tests for doctor creation, specializations, schedules and business logic
 *
 * @package Care_API\Tests\Unit\Models
 * @version 1.0.0
 * @author  Descomplicar® <dev@descomplicar.pt>
 * @since   1.0.0
 */

namespace Care_API\Tests\Unit\Models;

use Care_API\Models\Doctor;
use Care_API\Models\Clinic;

class DoctorTest extends \Care_API_Test_Case {

    /**
     * Mock wpdb for database operations
     */
    private $mock_wpdb;

    /**
     * Setup before each test
     */
    public function setUp(): void {
        parent::setUp();

        // Mock wpdb global
        global $wpdb;
        $this->mock_wpdb = $this->createMock('wpdb');
        $wpdb = $this->mock_wpdb;
    }

    /**
     * Test doctor creation with specializations
     *
     * @covers Doctor::create
     * @covers Doctor::validate_doctor_data
     */
    public function test_doctor_creation_with_specializations() {
        // Arrange
        $valid_doctor_data = array(
            'first_name' => 'Dr. António',
            'last_name' => 'Carvalho',
            'user_email' => 'dr.carvalho@clinica.com',
            'specialization' => 'Cardiologia',
            'qualification' => 'MD, PhD em Cardiologia',
            'experience_years' => 15,
            'mobile_number' => '+351912345678',
            'address' => 'Av. da República, 50',
            'city' => 'Porto',
            'country' => 'Portugal',
            'license_number' => 'OM12345',
            'consultation_fee' => 75.00,
            'languages' => array('Portuguese', 'English', 'Spanish'),
            'working_hours' => array(
                'monday' => array('start_time' => '09:00', 'end_time' => '17:00'),
                'tuesday' => array('start_time' => '09:00', 'end_time' => '17:00'),
                'wednesday' => array('start_time' => '09:00', 'end_time' => '17:00'),
                'thursday' => array('start_time' => '09:00', 'end_time' => '17:00'),
                'friday' => array('start_time' => '09:00', 'end_time' => '17:00')
            ),
            'clinic_id' => 1
        );

        // Mock WordPress functions
        $this->mock_wp_functions_for_doctor_creation();

        // Act
        $result = Doctor::create($valid_doctor_data);

        // Assert
        $this->assertIsInt($result, 'Doctor creation should return user ID');
        $this->assertGreaterThan(0, $result, 'User ID should be positive');
    }

    /**
     * Test doctor creation with missing required specialization
     *
     * @covers Doctor::create
     * @covers Doctor::validate_doctor_data
     */
    public function test_doctor_creation_missing_specialization() {
        // Arrange
        $invalid_doctor_data = array(
            'first_name' => 'Dr. Maria',
            'last_name' => 'Silva',
            'user_email' => 'dr.silva@clinica.com',
            // Missing required field: specialization
            'qualification' => 'MD'
        );

        // Act
        $result = Doctor::create($invalid_doctor_data);

        // Assert
        $this->assertInstanceOf('WP_Error', $result, 'Should return WP_Error for missing specialization');
        $this->assertEquals('doctor_validation_failed', $result->get_error_code());

        $error_data = $result->get_error_data();
        $this->assertArrayHasKey('errors', $error_data);
        $this->assertContains("Field 'specialization' is required", $error_data['errors']);
    }

    /**
     * Test doctor creation with invalid consultation fee
     *
     * @covers Doctor::validate_doctor_data
     */
    public function test_doctor_creation_invalid_consultation_fee() {
        // Arrange
        $doctor_data_invalid_fee = array(
            'first_name' => 'Dr. João',
            'last_name' => 'Costa',
            'user_email' => 'dr.costa@clinica.com',
            'specialization' => 'Dermatologia',
            'qualification' => 'MD',
            'consultation_fee' => 'not_a_number' // Invalid fee
        );

        // Act
        $result = Doctor::create($doctor_data_invalid_fee);

        // Assert
        $this->assertInstanceOf('WP_Error', $result);
        $error_data = $result->get_error_data();
        $this->assertContains('Invalid consultation fee. Must be a number', $error_data['errors']);
    }

    /**
     * Test doctor creation with invalid experience years
     *
     * @covers Doctor::validate_doctor_data
     */
    public function test_doctor_creation_invalid_experience_years() {
        // Arrange
        $doctor_data_invalid_experience = array(
            'first_name' => 'Dr. Pedro',
            'last_name' => 'Martins',
            'user_email' => 'dr.martins@clinica.com',
            'specialization' => 'Pediatria',
            'qualification' => 'MD',
            'experience_years' => -5 // Invalid negative experience
        );

        // Act
        $result = Doctor::create($doctor_data_invalid_experience);

        // Assert
        $this->assertInstanceOf('WP_Error', $result);
        $error_data = $result->get_error_data();
        $this->assertContains('Invalid experience years. Must be a positive number', $error_data['errors']);
    }

    /**
     * Test doctor working hours validation and update
     *
     * @covers Doctor::update_schedule
     * @covers Doctor::validate_working_hours
     */
    public function test_doctor_schedule_validation() {
        // Arrange
        $doctor_id = $this->create_test_doctor();

        $valid_working_hours = array(
            'monday' => array('start_time' => '08:00', 'end_time' => '16:00'),
            'tuesday' => array('start_time' => '08:30', 'end_time' => '17:30'),
            'wednesday' => array('start_time' => '09:00', 'end_time' => '18:00'),
            'thursday' => array('start_time' => '08:00', 'end_time' => '16:00'),
            'friday' => array('start_time' => '08:00', 'end_time' => '15:00')
        );

        // Mock doctor exists check
        $this->mock_doctor_exists(true);

        // Mock update_user_meta
        global $wp_test_expectations;
        $wp_test_expectations['update_user_meta'] = true;

        // Act
        $result = Doctor::update_schedule($doctor_id, $valid_working_hours);

        // Assert
        $this->assertTrue($result, 'Working hours should be successfully updated');
    }

    /**
     * Test doctor schedule validation with invalid time format
     *
     * @covers Doctor::validate_working_hours
     */
    public function test_doctor_schedule_invalid_time_format() {
        // Arrange
        $doctor_id = $this->create_test_doctor();

        $invalid_working_hours = array(
            'monday' => array('start_time' => '25:00', 'end_time' => '16:00'), // Invalid hour
            'tuesday' => array('start_time' => '08:00', 'end_time' => '17:70')  // Invalid minutes
        );

        // Mock doctor exists check
        $this->mock_doctor_exists(true);

        // Act
        $result = Doctor::update_schedule($doctor_id, $invalid_working_hours);

        // Assert
        $this->assertInstanceOf('WP_Error', $result);
        $this->assertEquals('invalid_time_format', $result->get_error_code());
    }

    /**
     * Test doctor schedule validation with invalid day
     *
     * @covers Doctor::validate_working_hours
     */
    public function test_doctor_schedule_invalid_day() {
        // Arrange
        $doctor_id = $this->create_test_doctor();

        $invalid_working_hours = array(
            'invalid_day' => array('start_time' => '09:00', 'end_time' => '17:00')
        );

        // Mock doctor exists check
        $this->mock_doctor_exists(true);

        // Act
        $result = Doctor::update_schedule($doctor_id, $invalid_working_hours);

        // Assert
        $this->assertInstanceOf('WP_Error', $result);
        $this->assertEquals('invalid_day', $result->get_error_code());
    }

    /**
     * Test doctor statistics calculation
     *
     * @covers Doctor::get_statistics
     */
    public function test_doctor_statistics() {
        // Arrange
        $doctor_id = $this->create_test_doctor();

        // Mock database queries for statistics
        $this->mock_wpdb->expects($this->exactly(6))
                        ->method('get_var')
                        ->willReturnOnConsecutiveCalls(
                            25, // total_appointments
                            18, // unique_patients
                            3,  // appointments_today
                            8,  // appointments_this_week
                            22, // appointments_this_month
                            15  // completed_encounters
                        );

        // Mock get_user_meta for consultation fee
        global $wp_test_expectations;
        $wp_test_expectations['get_user_meta'] = 75.00;

        // Act
        $statistics = Doctor::get_statistics($doctor_id);

        // Assert
        $this->assertIsArray($statistics, 'Statistics should be an array');
        $this->assertArrayHasKey('total_appointments', $statistics);
        $this->assertArrayHasKey('total_patients', $statistics);
        $this->assertArrayHasKey('appointments_today', $statistics);
        $this->assertArrayHasKey('appointments_this_week', $statistics);
        $this->assertArrayHasKey('appointments_this_month', $statistics);
        $this->assertArrayHasKey('completed_encounters', $statistics);
        $this->assertArrayHasKey('revenue_this_month', $statistics);

        $this->assertEquals(25, $statistics['total_appointments']);
        $this->assertEquals(18, $statistics['total_patients']);
        $this->assertEquals(3, $statistics['appointments_today']);
        $this->assertEquals(8, $statistics['appointments_this_week']);
        $this->assertEquals(22, $statistics['appointments_this_month']);
        $this->assertEquals(15, $statistics['completed_encounters']);
        $this->assertEquals(1650.00, $statistics['revenue_this_month']); // 22 * 75.00
    }

    /**
     * Test doctor appointments retrieval with filters
     *
     * @covers Doctor::get_appointments
     */
    public function test_doctor_appointments_retrieval() {
        // Arrange
        $doctor_id = $this->create_test_doctor();

        $appointment_filters = array(
            'status' => 1, // scheduled
            'date_from' => '2024-01-01',
            'date_to' => '2024-01-31',
            'limit' => 10
        );

        // Mock database query results
        $mock_appointments = array(
            array(
                'id' => 1,
                'appointment_start_date' => '2024-01-15',
                'appointment_start_time' => '10:00:00',
                'appointment_end_date' => '2024-01-15',
                'appointment_end_time' => '10:30:00',
                'visit_type' => 'consultation',
                'status' => 1,
                'patient_id' => 100,
                'patient_name' => 'João Silva',
                'clinic_id' => 1,
                'clinic_name' => 'Clínica Central',
                'description' => 'Consulta de rotina',
                'appointment_report' => '',
                'created_at' => '2024-01-10 14:00:00'
            ),
            array(
                'id' => 2,
                'appointment_start_date' => '2024-01-16',
                'appointment_start_time' => '14:00:00',
                'appointment_end_date' => '2024-01-16',
                'appointment_end_time' => '14:30:00',
                'visit_type' => 'follow_up',
                'status' => 1,
                'patient_id' => 101,
                'patient_name' => 'Maria Santos',
                'clinic_id' => 1,
                'clinic_name' => 'Clínica Central',
                'description' => 'Consulta de seguimento',
                'appointment_report' => '',
                'created_at' => '2024-01-12 09:00:00'
            )
        );

        $this->mock_wpdb->expects($this->once())
                        ->method('get_results')
                        ->willReturn($mock_appointments);

        // Act
        $appointments = Doctor::get_appointments($doctor_id, $appointment_filters);

        // Assert
        $this->assertIsArray($appointments, 'Appointments should be an array');
        $this->assertCount(2, $appointments, 'Should return 2 appointments');

        // Verify appointment structure
        $appointment = $appointments[0];
        $this->assertArrayHasKey('id', $appointment);
        $this->assertArrayHasKey('start_date', $appointment);
        $this->assertArrayHasKey('start_time', $appointment);
        $this->assertArrayHasKey('patient', $appointment);
        $this->assertArrayHasKey('clinic', $appointment);

        $this->assertEquals(1, $appointment['id']);
        $this->assertEquals('2024-01-15', $appointment['start_date']);
        $this->assertEquals('consultation', $appointment['visit_type']);
        $this->assertEquals('João Silva', $appointment['patient']['name']);
    }

    /**
     * Test doctor clinic assignments (multiple clinics)
     *
     * @covers Doctor::assign_to_clinic
     * @covers Doctor::get_doctor_full_data
     */
    public function test_doctor_multiple_clinic_assignments() {
        // Arrange
        $doctor_id = $this->create_test_doctor();
        $clinic_id_1 = 1;
        $clinic_id_2 = 2;

        // Mock clinic exists checks
        $clinic_mock = $this->createMock('Care_API\Models\Clinic');
        $clinic_mock->method('exists')->willReturn(true);

        // Mock wpdb operations for first assignment
        $this->mock_wpdb->expects($this->exactly(2))
                        ->method('get_var')
                        ->willReturn(0); // No existing mappings

        $this->mock_wpdb->expects($this->exactly(2))
                        ->method('insert')
                        ->willReturn(1); // Successful inserts

        // Act
        $result1 = Doctor::assign_to_clinic($doctor_id, $clinic_id_1);
        $result2 = Doctor::assign_to_clinic($doctor_id, $clinic_id_2);

        // Assert
        $this->assertTrue($result1, 'Doctor should be assigned to first clinic');
        $this->assertTrue($result2, 'Doctor should be assigned to second clinic');
    }

    /**
     * Helper method to create test doctor
     */
    private function create_test_doctor() {
        return $this->factory->user->create(array(
            'user_login' => 'test_doctor_' . wp_rand(1000, 9999),
            'user_email' => 'testdoctor' . wp_rand(1000, 9999) . '@example.com',
            'first_name' => 'Dr. Test',
            'last_name' => 'Doctor',
            'role' => 'kivicare_doctor'
        ));
    }

    /**
     * Helper method to mock WordPress functions for doctor creation
     */
    private function mock_wp_functions_for_doctor_creation() {
        global $wp_test_expectations;

        // Mock successful user creation
        $wp_test_expectations['wp_insert_user'] = 123;
        $wp_test_expectations['is_email'] = true;
        $wp_test_expectations['get_user_by'] = false; // No existing user
        $wp_test_expectations['username_exists'] = false; // Username available
        $wp_test_expectations['sanitize_email'] = function($email) { return $email; };
        $wp_test_expectations['sanitize_text_field'] = function($text) { return $text; };
        $wp_test_expectations['sanitize_textarea_field'] = function($text) { return $text; };
        $wp_test_expectations['wp_generate_password'] = 'test_password';
        $wp_test_expectations['current_time'] = '2024-01-15 10:30:00';
        $wp_test_expectations['update_user_meta'] = true;
        $wp_test_expectations['wp_json_encode'] = function($data) { return json_encode($data); };
    }

    /**
     * Helper method to mock doctor exists check
     */
    private function mock_doctor_exists($exists = true) {
        global $wp_test_expectations;

        if ($exists) {
            $mock_user = (object) array(
                'ID' => 123,
                'roles' => array('kivicare_doctor')
            );
            $wp_test_expectations['get_user_by'] = $mock_user;
        } else {
            $wp_test_expectations['get_user_by'] = false;
        }
    }
}