<?php

namespace Tests\Unit\Models;

use App\Libraries\enums\JenisKelamin;
use App\Models\GuruModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;

/**
 * @internal
 */
final class GuruModelTest extends CIUnitTestCase
{
    use DatabaseTestTrait;

    protected $migrate     = true;
    protected $migrateOnce = true;
    protected $refresh     = true;
    protected $namespace   = null;

    protected GuruModel $model;

    protected function setUp(): void
    {
        parent::setUp();
        $this->model = new GuruModel();
    }
    
    protected function tearDown(): void
    {
        parent::tearDown();
        $this->db->table('tb_guru')->delete('1 = 1');
    }

    // =====================================================
    // HAPPY PATH TESTS - CREATE OPERATIONS
    // =====================================================

    public function testCreateGuruSuccessfully(): void
    {
        $result = $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789'
        );
        
        $this->assertTrue($result);
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        $this->assertNotNull($guru);
        $this->assertEquals('John Teacher', $guru['nama_guru']);
        $this->assertEquals(JenisKelamin::LAKI_LAKI->value, $guru['jenis_kelamin']);
        $this->assertEquals('Jl. Pendidikan No. 1', $guru['alamat']);
        $this->assertEquals('08123456789', $guru['no_hp']);
        $this->assertNotEmpty($guru['unique_code']);
    }

    public function testCreateGuruWithRfid(): void
    {
        $result = $this->model->createGuru(
            '1234567890123456',
            'Jane Teacher',
            JenisKelamin::PEREMPUAN->value,
            'Jl. Pendidikan No. 2',
            '08123456788',
            'RFID123456'
        );
        
        $this->assertTrue($result);
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        $this->assertEquals('RFID123456', $guru['rfid_code']);
    }

    public function testCreateGuruGeneratesUniqueCode(): void
    {
        $result = $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789'
        );
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        // Check unique_code format - should be generated by hash
        $this->assertNotEmpty($guru['unique_code']);
        $this->assertIsString($guru['unique_code']);
        $this->assertGreaterThan(40, strlen($guru['unique_code'])); // SHA1 (40) + partial SHA1 (24) = 64
    }

    // =====================================================
    // HAPPY PATH TESTS - READ OPERATIONS
    // =====================================================

    public function testCekGuruByUniqueCode(): void
    {
        $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789'
        );
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        $result = $this->model->cekGuru($guru['unique_code']);
        
        $this->assertNotNull($result);
        $this->assertEquals('John Teacher', $result['nama_guru']);
    }

    public function testCekGuruByRfidCode(): void
    {
        $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789',
            'RFID123456'
        );
        
        $result = $this->model->cekGuru('RFID123456');
        
        $this->assertNotNull($result);
        $this->assertEquals('John Teacher', $result['nama_guru']);
        $this->assertEquals('RFID123456', $result['rfid_code']);
    }

    public function testGetAllGuru(): void
    {
        $this->model->createGuru('1234567890123456', 'John Teacher', JenisKelamin::LAKI_LAKI->value, 'Jl. Test 1', '08123456789');
        $this->model->createGuru('1234567890123457', 'Jane Teacher', JenisKelamin::PEREMPUAN->value, 'Jl. Test 2', '08123456788');
        
        $result = $this->model->getAllGuru();
        
        $this->assertIsArray($result);
        $this->assertCount(2, $result);
    }

    public function testGetGuruById(): void
    {
        $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789'
        );
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        $result = $this->model->getGuruById($guru['id_guru']);
        
        $this->assertNotNull($result);
        $this->assertEquals('John Teacher', $result['nama_guru']);
    }

    // =====================================================
    // HAPPY PATH TESTS - UPDATE OPERATIONS
    // =====================================================

    public function testUpdateGuru(): void
    {
        $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789'
        );
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        $result = $this->model->updateGuru(
            $guru['id_guru'],
            '1234567890123456',
            'John Updated',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Updated Address',
            '08123456780'
        );
        
        $this->assertTrue($result);
        
        $updatedGuru = $this->model->getGuruById($guru['id_guru']);
        
        $this->assertEquals('John Updated', $updatedGuru['nama_guru']);
        $this->assertEquals('Jl. Updated Address', $updatedGuru['alamat']);
        $this->assertEquals('08123456780', $updatedGuru['no_hp']);
    }

    public function testUpdateGuruWithRfid(): void
    {
        $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789'
        );
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        $result = $this->model->updateGuru(
            $guru['id_guru'],
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789',
            'RFID789456'
        );
        
        $this->assertTrue($result);
        
        $updatedGuru = $this->model->getGuruById($guru['id_guru']);
        
        $this->assertEquals('RFID789456', $updatedGuru['rfid_code']);
    }

    // =====================================================
    // UNHAPPY PATH TESTS
    // =====================================================

    public function testCekGuruWithInvalidCode(): void
    {
        $result = $this->model->cekGuru('invalid-code-999');
        
        $this->assertNull($result);
    }

    public function testGetGuruByIdWithInvalidId(): void
    {
        $result = $this->model->getGuruById(99999);
        
        $this->assertNull($result);
    }

    public function testGetAllGuruWhenEmpty(): void
    {
        $result = $this->model->getAllGuru();
        
        $this->assertIsArray($result);
        $this->assertEmpty($result);
    }

    // =====================================================
    // EDGE CASES
    // =====================================================

    public function testCreateGuruGeneratesDifferentUniqueCodes(): void
    {
        $this->model->createGuru('1234567890123456', 'John Teacher', JenisKelamin::LAKI_LAKI->value, 'Jl. Test 1', '08123456789');
        $this->model->createGuru('1234567890123457', 'Jane Teacher', JenisKelamin::PEREMPUAN->value, 'Jl. Test 2', '08123456788');
        
        $guru1 = $this->db->table('tb_guru')->where('nuptk', '1234567890123456')->get()->getRowArray();
        $guru2 = $this->db->table('tb_guru')->where('nuptk', '1234567890123457')->get()->getRowArray();
        
        $this->assertNotEquals($guru1['unique_code'], $guru2['unique_code']);
    }

    public function testUpdateGuruDoesNotChangeUniqueCode(): void
    {
        $this->model->createGuru(
            '1234567890123456',
            'John Teacher',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Pendidikan No. 1',
            '08123456789'
        );
        
        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();
        
        $originalUniqueCode = $guru['unique_code'];
        
        $this->model->updateGuru(
            $guru['id_guru'],
            '1234567890123456',
            'John Updated',
            JenisKelamin::LAKI_LAKI->value,
            'Jl. Updated Address',
            '08123456780'
        );
        
        $updatedGuru = $this->model->getGuruById($guru['id_guru']);
        
        $this->assertEquals($originalUniqueCode, $updatedGuru['unique_code']);
    }

    public function testGetAllGuruOrderedByName(): void
    {
        $this->model->createGuru('1234567890123456', 'Zack Teacher', JenisKelamin::LAKI_LAKI->value, 'Jl. Test 1', '08123456789');
        $this->model->createGuru('1234567890123457', 'Alice Teacher', JenisKelamin::PEREMPUAN->value, 'Jl. Test 2', '08123456788');
        $this->model->createGuru('1234567890123458', 'Bob Teacher', JenisKelamin::LAKI_LAKI->value, 'Jl. Test 3', '08123456787');
        
        $result = $this->model->getAllGuru();
        
        $this->assertEquals('Alice Teacher', $result[0]['nama_guru']);
        $this->assertEquals('Bob Teacher', $result[1]['nama_guru']);
        $this->assertEquals('Zack Teacher', $result[2]['nama_guru']);
    }

    public function testCekGuruReturnsFirstMatchForOrCondition(): void
    {
        // Test that cekGuru works with either unique_code OR rfid_code
        $this->model->createGuru('1234567890123456', 'John Teacher', JenisKelamin::LAKI_LAKI->value, 'Jl. Test', '08123456789', 'RFID123');
        
        $guru = $this->db->table('tb_guru')->where('nuptk', '1234567890123456')->get()->getRowArray();
        
        $resultByUniqueCode = $this->model->cekGuru($guru['unique_code']);
        $resultByRfid = $this->model->cekGuru('RFID123');
        
        $this->assertNotNull($resultByUniqueCode);
        $this->assertNotNull($resultByRfid);
        $this->assertEquals($resultByUniqueCode['id_guru'], $resultByRfid['id_guru']);
    }

    public function testGenerateCSVObjectSuccessfully(): void
    {
        $csvContent = "nuptk,nama_guru,jenis_kelamin,alamat,no_hp\n" .
                      "1234567890123456,Guru One,L,Jl. Test 1,08111111111\n" .
                      "9876543210987654,Guru Two,P,Jl. Test 2,08222222222\n";

        $tmpDir = FCPATH . 'uploads/tmp/';
        if (!is_dir($tmpDir)) {
            mkdir($tmpDir, 0777, true);
        }

        $tempFile = $tmpDir . 'test_guru_' . uniqid() . '.csv';
        file_put_contents($tempFile, $csvContent);

        $result = $this->model->generateCSVObject($tempFile);

        $this->assertNotEmpty($result);
        $this->assertEquals(2, $result->numberOfItems);
        $this->assertNotEmpty($result->txtFileName);

        $txtFile = $tmpDir . $result->txtFileName;
        $this->assertFileExists($txtFile);

        @unlink($txtFile);
    }

    public function testGenerateCSVObjectWithBOM(): void
    {
        $csvContent = "\xEF\xBB\xBFnuptk,nama_guru,jenis_kelamin,alamat,no_hp\n" .
                      "1234567890123456,Guru Test,L,Jl. Test,08111111111\n";

        $tmpDir = FCPATH . 'uploads/tmp/';
        if (!is_dir($tmpDir)) {
            mkdir($tmpDir, 0777, true);
        }

        $tempFile = $tmpDir . 'test_guru_bom_' . uniqid() . '.csv';
        file_put_contents($tempFile, $csvContent);

        $result = $this->model->generateCSVObject($tempFile);

        $this->assertNotEmpty($result);
        $this->assertEquals(1, $result->numberOfItems);

        $txtFile = FCPATH . 'uploads/tmp/' . $result->txtFileName;
        @unlink($txtFile);
    }

    public function testImportCSVItemSuccessfully(): void
    {
        $testData = [
            [
                'nuptk' => '1234567890123456',
                'nama_guru' => 'Imported Guru',
                'jenis_kelamin' => 'L',
                'alamat' => 'Jl. Import Test',
                'no_hp' => '08123456789'
            ]
        ];

        $tmpDir = FCPATH . 'uploads/tmp/';
        if (!is_dir($tmpDir)) {
            mkdir($tmpDir, 0777, true);
        }

        $txtFileName = 'test_' . uniqid() . '.txt';
        $txtFile = fopen($tmpDir . $txtFileName, 'w');
        fwrite($txtFile, serialize($testData));
        fclose($txtFile);

        $result = $this->model->importCSVItem($txtFileName, 1);

        $this->assertNotEmpty($result);
        $this->assertEquals('1234567890123456', $result['nuptk']);
        $this->assertEquals('Imported Guru', $result['nama_guru']);
        $this->assertNotEmpty($result['unique_code']);

        $guru = $this->db->table('tb_guru')
            ->where('nuptk', '1234567890123456')
            ->get()
            ->getRowArray();

        $this->assertNotNull($guru);
        $this->assertEquals('Imported Guru', $guru['nama_guru']);

        @unlink($tmpDir . $txtFileName);
    }

    public function testImportCSVItemWithMultipleRecords(): void
    {
        $testData = [
            [
                'nuptk' => '1111111111111111',
                'nama_guru' => 'First Guru',
                'jenis_kelamin' => 'L',
                'alamat' => 'Jl. First',
                'no_hp' => '08111111111'
            ],
            [
                'nuptk' => '2222222222222222',
                'nama_guru' => 'Second Guru',
                'jenis_kelamin' => 'P',
                'alamat' => 'Jl. Second',
                'no_hp' => '08222222222'
            ]
        ];

        $tmpDir = FCPATH . 'uploads/tmp/';
        if (!is_dir($tmpDir)) {
            mkdir($tmpDir, 0777, true);
        }

        $txtFileName = 'test_multi_' . uniqid() . '.txt';
        $txtFile = fopen($tmpDir . $txtFileName, 'w');
        fwrite($txtFile, serialize($testData));
        fclose($txtFile);

        $result = $this->model->importCSVItem($txtFileName, 2);

        $this->assertNotEmpty($result);
        $this->assertEquals('2222222222222222', $result['nuptk']);
        $this->assertEquals('Second Guru', $result['nama_guru']);

        @unlink($tmpDir . $txtFileName);
    }

    public function testGenerateCSVObjectWithEmptyFile(): void
    {
        $csvContent = "nuptk,nama_guru,jenis_kelamin,alamat,no_hp\n";

        $tmpDir = FCPATH . 'uploads/tmp/';
        if (!is_dir($tmpDir)) {
            mkdir($tmpDir, 0777, true);
        }

        $tempFile = $tmpDir . 'test_empty_' . uniqid() . '.csv';
        file_put_contents($tempFile, $csvContent);

        $result = $this->model->generateCSVObject($tempFile);

        $this->assertFalse($result);
    }
}
