<?php


namespace Tests\App\Controllers\Api;

use App\Controllers\Api\AdministratorController;
use CodeIgniter\Test\CIUnitTestCase;
use Config\Services;

class AdministratorControllerTest extends CIUnitTestCase
{
    protected $controller;

    protected function setUp(): void
    {
        parent::setUp();

        // ✅ Create a fake database connection object
        $fakeDb = $this->createMock(\CodeIgniter\Database\BaseConnection::class);

        // ✅ Stub Config\Database::connect() globally using class_alias
        //    so CodeIgniter's static Database::connect() returns our fake DB
        if (!class_exists('Config\\FakeDatabase')) {
            eval('namespace Config; class FakeDatabase extends \\Config\\Database { 
                public static function connect($group = null, bool $getShared = true) { 
                    return $GLOBALS["__fakeDb__"]; 
                } 
            }');
        }
        $GLOBALS['__fakeDb__'] = $fakeDb;

        // Temporarily alias Config\Database to our FakeDatabase
        class_alias('Config\\FakeDatabase', 'Config\\Database');

        // ✅ Mock all required models/services
        foreach ([
            \App\Models\UserModel::class,
            \App\Models\StudentModel::class,
            \App\Models\ConfigurationModel::class,
            \App\Models\LoginActivityModel::class,
            \App\Models\EnrollmentModel::class,
            \App\Models\RefundModel::class,
            \App\Models\InvoiceModel::class,
            \App\Models\ClassSectionModel::class,
            \App\Models\UserRoleModel::class,
            \App\Models\StudentClassModel::class,
            \App\Models\StaffAttendanceModel::class,
            \App\Services\FeeCalculationService::class,
        ] as $class) {
            $this->mockModel($class);
        }

        // ✅ Configuration mock
        $configMock = $this->mockModel(\App\Models\ConfigurationModel::class);
        $configMock->method('getConfig')->willReturnMap([
            ['school_year', '2024-2025'],
            ['semester', 'Fall'],
        ]);

        // ✅ Instantiate controller safely (no DB hit)
        $this->controller = new AdministratorController();
    }

    protected function mockModel(string $class)
    {
        $mock = $this->createMock($class);
        Services::injectMock($class, $mock);
        return $mock;
    }

    /** @test */
    public function testDashboardSuccess()
    {
        $result = $this->controller->dashboard();
        $this->assertNotNull($result);
    }

    /* --------------------------------------------------------------------------
     *  absenceInfo()
     * -------------------------------------------------------------------------- */
    public function testAbsenceInfoWithValidUser()
    {
        helper('date');
        $this->controller->setSession(['user_id' => 7]);

        $userMock  = model(UserModel::class);
        $staffMock = model(StaffAttendanceModel::class);

        $userMock->method('find')->willReturn(['firstname' => 'Jane', 'lastname' => 'Admin']);
        $staffMock->method('where')->willReturnSelf();
        $staffMock->method('orderBy')->willReturnSelf();
        $staffMock->method('findAll')->willReturn([
            ['date' => '2025-09-14', 'status' => 'present'],
        ]);

        $response = $this->controller->absenceInfo();
        $this->assertTrue($response['status']);
        $this->assertArrayHasKey('available_dates', $response['data']);
    }

    public function testAbsenceInfoUnauthorized()
    {
        $this->controller->setSession([]); // no user
        $response = $this->controller->absenceInfo();
        $this->assertFalse($response['status']);
        $this->assertEquals(401, $response['code']);
    }

    /* --------------------------------------------------------------------------
     *  submitAbsence()
     * -------------------------------------------------------------------------- */
    public function testSubmitAbsenceRejectsMissingReason()
    {
        $this->controller->setSession(['user_id' => 2]);
        $req = $this->makeRequest('POST', '/api/v1/administrator/absence', [
            'dates' => ['2025-10-05'],
            'reason' => '',
        ]);
        $this->controller->setRequest($req);
        $resp = $this->controller->submitAbsence();
        $this->assertFalse($resp['status']);
        $this->assertEquals('Reason is required', $resp['message']);
    }

    public function testSubmitAbsenceValidDateAndReason()
    {
        $this->controller->setSession(['user_id' => 5]);

        $req = $this->makeRequest('POST', '/api/v1/administrator/absence', [
            'dates' => ['2025-09-07'],
            'reason_type' => 'Personal',
            'reason' => 'Travel',
        ]);
        $this->controller->setRequest($req);

        $staffMock = model(StaffAttendanceModel::class);
        $userMock  = model(UserModel::class);

        $userMock->method('getUserRole')->willReturn('admin');
        $staffMock->method('upsertOne')->willReturn(true);
        $userMock->method('find')->willReturn(['firstname' => 'Sarah', 'lastname' => 'Ali', 'email' => 's@a.com']);

        $email = Services::email();
        $email->clear();

        $resp = $this->controller->submitAbsence();
        $this->assertTrue($resp['status']);
        $this->assertArrayHasKey('saved_dates', $resp['data']);
    }

    /* --------------------------------------------------------------------------
     *  search()
     * -------------------------------------------------------------------------- */
    public function testSearchRequiresQueryParam()
    {
        $this->controller->setRequest($this->makeRequest('GET', '/api/v1/administrator/search?q='));
        $resp = $this->controller->search();
        $this->assertFalse($resp['status']);
        $this->assertEquals('Search query is required', $resp['message']);
    }

    public function testSearchReturnsStructuredResults()
    {
        $this->controller->setRequest($this->makeRequest('GET', '/api/v1/administrator/search?q=john'));

        $db = $this->createMock(\CodeIgniter\Database\BaseConnection::class);
        $builder = $this->createMock(\CodeIgniter\Database\BaseBuilder::class);
        $builder->method('select')->willReturnSelf();
        $builder->method('limit')->willReturnSelf();
        $builder->method('get')->willReturnSelf();
        $builder->method('getResultArray')->willReturn([
            ['id' => 1, 'firstname' => 'John', 'lastname' => 'Doe'],
        ]);
        $db->method('table')->willReturn($builder);
        service('database')->shouldReceive('connect')->andReturn($db);

        $resp = $this->controller->search();
        $this->assertTrue($resp['status']);
        $this->assertArrayHasKey('users', $resp['data']);
    }

    /* --------------------------------------------------------------------------
     *  enrollmentWithdrawal()
     * -------------------------------------------------------------------------- */
    public function testEnrollmentWithdrawalPagination()
    {
        $mock = model(EnrollmentModel::class);
        $mock->method('where')->willReturnSelf();
        $mock->method('orderBy')->willReturnSelf();
        $mock->method('findAll')->willReturn([
            ['student_id' => 1, 'status' => 'enrolled'],
        ]);

        $resp = $this->controller->enrollmentWithdrawal();
        $this->assertTrue($resp['status']);
        $this->assertEquals('Enrollment/withdrawal data retrieved successfully', $resp['message']);
    }

    /* --------------------------------------------------------------------------
     *  enrollmentWithdrawalData()
     * -------------------------------------------------------------------------- */
    public function testEnrollmentWithdrawalDataBuildsStudentList()
    {
        $stuMock = model(StudentModel::class);
        $stuMock->method('getStudentsWithClassAndEnrollment')->willReturn([
            ['id' => 1, 'firstname' => 'A', 'lastname' => 'Z', 'parent_id' => 10],
        ]);

        $clsMock = model(ClassSectionModel::class);
        $clsMock->method('select')->willReturnSelf();
        $clsMock->method('where')->willReturnSelf();
        $clsMock->method('orderBy')->willReturnSelf();
        $clsMock->method('findAll')->willReturn([
            ['id' => 1, 'class_section_name' => 'KG1'],
        ]);

        $enrollMock = model(EnrollmentModel::class);
        $enrollMock->method('getEnrollmentStatus')->willReturn('enrolled');

        $resp = $this->controller->enrollmentWithdrawalData();
        $this->assertTrue($resp['status']);
        $this->assertArrayHasKey('students', $resp['data']);
        $this->assertArrayHasKey('classes', $resp['data']);
    }

    /* --------------------------------------------------------------------------
     *  updateEnrollmentStatuses()
     * -------------------------------------------------------------------------- */
    public function testUpdateEnrollmentStatusesRejectsEmptyPayload()
    {
        $req = $this->makeRequest('POST', '/api/v1/administrator/enrollment-withdrawal/update', []);
        $this->controller->setRequest($req);
        $resp = $this->controller->updateEnrollmentStatuses();
        $this->assertFalse($resp['status']);
        $this->assertEquals('No enrollment statuses were submitted.', $resp['message']);
    }

    public function testUpdateEnrollmentStatusesWithValidStatus()
    {
        $req = $this->makeRequest('POST', '/api/v1/administrator/enrollment-withdrawal/update', [
            'enrollment_status' => [1 => 'enrolled']
        ]);
        $this->controller->setRequest($req);

        $stuMock  = model(StudentModel::class);
        $stuMock->method('find')->willReturn(['id' => 1, 'parent_id' => 2]);

        $enrollMock = model(EnrollmentModel::class);
        $enrollMock->method('where')->willReturnSelf();
        $enrollMock->method('get')->willReturnSelf();
        $enrollMock->method('getRowArray')->willReturn(['enrollment_status' => 'payment pending', 'parent_id' => 2]);

        $invoiceMock = model(InvoiceModel::class);
        $invoiceMock->method('where')->willReturnSelf();
        $invoiceMock->method('orderBy')->willReturnSelf();
        $invoiceMock->method('first')->willReturn(['id' => 10, 'school_year' => '2024-2025']);

        $refundMock = model(RefundModel::class);
        $refundMock->method('where')->willReturnSelf();
        $refundMock->method('first')->willReturn(null);

        $resp = $this->controller->updateEnrollmentStatuses();
        $this->assertTrue($resp['status']);
        $this->assertArrayHasKey('groupsByParentStatus', $resp['data']);
    }

    /* --------------------------------------------------------------------------
     *  studentProfiles()
     * -------------------------------------------------------------------------- */
    public function testStudentProfilesReturnsList()
    {
        $studClassMock = model(StudentClassModel::class);
        $studClassMock->method('getClassSectionNameByStudentId')->willReturn('KG2');

        $resp = $this->controller->studentProfiles();
        $this->assertTrue($resp['status']);
        $this->assertArrayHasKey('students', $resp['data']);
    }

    /* --------------------------------------------------------------------------
     *  parentProfiles()
     * -------------------------------------------------------------------------- */
    public function testParentProfilesWithParentRole()
    {
        $userMock = model(UserModel::class);
        $userMock->method('findAll')->willReturn([
            ['id' => 20, 'firstname' => 'Layla', 'lastname' => 'Yusuf', 'email' => 'layla@test.com', 'cellphone' => '123', 'school_id' => 'P1'],
        ]);

        $roleMock = model(UserRoleModel::class);
        $roleMock->method('getRolesByUserId')->willReturn([
            ['role_name' => 'parent'],
        ]);

        $invMock = model(InvoiceModel::class);
        $invMock->method('getLatestInvoicePaidAmount')->willReturn(150.00);
        $invMock->method('getLatestInvoiceBalance')->willReturn(50.00);

        $resp = $this->controller->parentProfiles();
        $this->assertTrue($resp['status']);
        $this->assertArrayHasKey('parents', $resp['data']);
        $this->assertEquals('Layla', $resp['data']['parents'][0]['firstname']);
    }
}
