<?php

namespace Tests\App\Controllers\Api;

use App\Controllers\Api\AttendanceController;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\URI;
use CodeIgniter\HTTP\UserAgent;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockIncomingRequest;
use Config\App;

class FakeAttendanceRequest extends MockIncomingRequest
{
    public function __construct(private readonly ?array $json = null, private readonly array $gets = [])
    {
        parent::__construct(config(App::class), new URI('https://test.alrahmaisgl.org'), 'php://input', new UserAgent());
    }

    public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0): ?array
    {
        return $this->json;
    }

    public function getGet($key = null, $filter = null, $default = null)
    {
        if ($key === null) {
            return $this->gets;
        }

        return $this->gets[$key] ?? $default;
    }
}

class StubPager
{
    public function __construct(private readonly int $current, private readonly int $perPage, private readonly int $total)
    {
    }

    public function getCurrentPage()
    {
        return $this->current;
    }

    public function getPerPage()
    {
        return $this->perPage;
    }

    public function getTotal()
    {
        return $this->total;
    }

    public function getPageCount()
    {
        return (int) ceil($this->total / max(1, $this->perPage));
    }
}

class StubAttendanceModel
{
    public array $whereHistory = [];
    public array $findResult = [];
    public array $findAllResult = [];
    public array $saved = [];
    public array $attendanceLookup = [];
    public int $insertId = 0;
    public array $pagerData = ['current' => 1, 'perPage' => 20, 'total' => 0];
    public array $firstQueue = [];
    public array $updatedAttendance = [];

    public function where(string $column, $value = null): self
    {
        $this->whereHistory[$column] = $value;
        return $this;
    }

    public function paginate(int $perPage, string $group, int $page)
    {
        $this->pager = new StubPager($this->pagerData['current'], $this->pagerData['perPage'], $this->pagerData['total']);
        return $this->findAllResult;
    }

    public function find($id)
    {
        return $this->findResult[$id] ?? null;
    }

    public function insert(array $data)
    {
        $this->insertId++;
        $this->saved[$this->insertId] = $data;
        $this->findResult[$this->insertId] = ['id' => $this->insertId] + $data;
        return $this->insertId;
    }

    public function getInsertID()
    {
        return $this->insertId;
    }

    public function update($id, array $data)
    {
        $this->saved[$id] = $data;
        $this->findResult[$id] = ['id' => $id] + $data;
    }

    public function delete($id)
    {
        unset($this->findResult[$id]);
    }

    public function first()
    {
        return array_shift($this->firstQueue) ?? null;
    }

    public function queueFirst($row): void
    {
        $this->firstQueue[] = $row;
    }

    public function findAll(): array
    {
        return $this->findAllResult;
    }

    public function getAttendance(int $studentId, int $classSectionId, string $date): array
    {
        return $this->attendanceLookup[$studentId] ?? [];
    }

    public function setAttendanceLookup(int $studentId, array $row): void
    {
        $this->attendanceLookup[$studentId] = $row;
    }

    public function updateAttendance(int $id, array $data): bool
    {
        $this->updatedAttendance[$id] = $data;
        $this->findResult[$id] = ['id' => $id] + $data;
        return true;
    }

    public function orderBy(...$args): self
    {
        return $this;
    }
}

class StubStudentClassModel
{
    public ?string $grade = null;

    public function getStudentGrade(int $studentId)
    {
        return $this->grade;
    }
}

class StubConfigModel
{
    public function __construct(private readonly array $values)
    {
    }

    public function getConfig(string $key)
    {
        return $this->values[$key] ?? null;
    }
}

class StubDbTable
{
    private array $results = [];

    public function __construct(array $rows)
    {
        $this->results = $rows;
    }

    public function select(...$args)
    {
        return $this;
    }

    public function join(...$args)
    {
        return $this;
    }

    public function where(...$args)
    {
        return $this;
    }

    public function orderBy(...$args)
    {
        return $this;
    }

    public function get()
    {
        return $this;
    }

    public function getResultArray()
    {
        return $this->results;
    }
}

class StubDbConnection
{
    public function __construct(private readonly array $rows)
    {
    }

    public function table($name)
    {
        return new StubDbTable($this->rows);
    }
}

class TestableAttendanceController extends AttendanceController
{
    public bool $validationResult = true;
    public array $validationErrors = [];
    public $validator;
    private ?StubDbConnection $db = null;

    public function __construct()
    {
    }

    public function setModels(StubAttendanceModel $attendanceModel, StubStudentClassModel $studentClassModel, StubConfigModel $configModel): void
    {
        $this->attendanceModel = $attendanceModel;
        $this->studentClassModel = $studentClassModel;
        $this->configModel = $configModel;
        $this->schoolYear = (string) $configModel->getConfig('school_year');
        $this->semester = (string) $configModel->getConfig('semester');
    }

    public function setDatabaseConnection(StubDbConnection $db): void
    {
        $this->db = $db;
    }

    public function setValidationResult(bool $result, array $errors = []): void
    {
        $this->validationResult = $result;
        $this->validationErrors = $errors;
    }

    protected function validate($rules, array $messages = []): bool
    {
        $this->validator = new class($this->validationErrors) {
            private array $errors;

            public function __construct(array $errors)
            {
                $this->errors = $errors;
            }

            public function getErrors()
            {
                return $this->errors;
            }
        };

        return $this->validationResult;
    }

    protected function getDatabaseConnection()
    {
        return $this->db ?? parent::getDatabaseConnection();
    }

    protected function respond($data = null, ?int $status = null, string $message = '')
    {
        return ['data' => $data, 'code' => $status, 'message' => $message];
    }

    protected function error(string $message = 'An error occurred', int $code = 400, ?array $errors = null)
    {
        $payload = ['status' => false, 'message' => $message, 'code' => $code];
        if (!empty($errors)) {
            $payload['errors'] = $errors;
        }
        return $payload;
    }
}

class AttendanceControllerTest extends CIUnitTestCase
{
    private TestableAttendanceController $controller;
    private StubAttendanceModel $attendanceModel;
    private StubStudentClassModel $studentClassModel;
    private StubConfigModel $configModel;

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

        $this->controller = new TestableAttendanceController();
        $this->attendanceModel = new StubAttendanceModel();
        $this->studentClassModel = new StubStudentClassModel();
        $this->configModel = new StubConfigModel(['school_year' => '2024-2025', 'semester' => 'Spring']);
        $this->controller->setModels($this->attendanceModel, $this->studentClassModel, $this->configModel);
    }

    public function testIndexUsesFilters()
    {
        $request = new FakeAttendanceRequest(null, ['student_id' => '5', 'date' => '2024-09-01', 'page' => '2', 'per_page' => '10']);
        $this->attendanceModel->findAllResult = [['id' => 1]];
        $this->attendanceModel->pagerData = ['current' => 2, 'perPage' => 10, 'total' => 30];
        $this->controller->setRequest($request);

        $response = $this->controller->index();

        $this->assertSame('Attendance records retrieved successfully', $response['message']);
        $this->assertSame(2, $response['data']['pagination']['current_page']);
        $this->assertSame(1, $response['data']['data'][0]['id']);
    }

    public function testShowNotFound()
    {
        $response = $this->controller->show(7);
        $this->assertSame(ResponseInterface::HTTP_NOT_FOUND, $response['code']);
    }

    public function testShowSuccess()
    {
        $this->attendanceModel->findResult[8] = ['id' => 8];
        $response = $this->controller->show(8);
        $this->assertSame('Attendance record retrieved successfully', $response['message']);
    }

    public function testStoreValidationFails()
    {
        $this->controller->setValidationResult(false, ['status' => 'required']);
        $request = new FakeAttendanceRequest(['student_id'=>1,'date'=>'','status'=>'']);
        $this->controller->setRequest($request);

        $response = $this->controller->store();

        $this->assertSame(422, $response['code']);
        $this->assertArrayHasKey('status', $response['errors']);
    }

    public function testStoreAlreadyRecorded()
    {
        $request = new FakeAttendanceRequest(['student_id'=>2,'date'=>'2024-09-01','status'=>'present']);
        $this->controller->setRequest($request);
        $this->attendanceModel->queueFirst(['id'=>5]);

        $response = $this->controller->store();

        $this->assertSame(409, $response['code']);
    }

    public function testStoreCreatesRecord()
    {
        $request = new FakeAttendanceRequest(['student_id'=>2,'date'=>'2024-09-01','status'=>'present','notes'=>'ok']);
        $this->controller->setRequest($request);
        $this->controller->setValidationResult(true);

        $response = $this->controller->store();

        $this->assertSame('Attendance record created successfully', $response['message']);
        $this->assertSame(1, $this->attendanceModel->insertId);
    }

    public function testUpdateNotFound()
    {
        $response = $this->controller->update(99);
        $this->assertSame(ResponseInterface::HTTP_NOT_FOUND, $response['code']);
    }

    public function testUpdateValidationFails()
    {
        $this->attendanceModel->findResult[12] = ['id' => 12];
        $this->controller->setValidationResult(false, ['status' => 'invalid']);
        $this->controller->setRequest(new FakeAttendanceRequest(['status'=>'bad']));

        $response = $this->controller->update(12);
        $this->assertSame(422, $response['code']);
    }

    public function testUpdateSuccess()
    {
        $this->attendanceModel->findResult[13] = ['id' => 13];
        $this->controller->setValidationResult(true);
        $this->controller->setRequest(new FakeAttendanceRequest(['status'=>'absent']));

        $response = $this->controller->update(13);
        $this->assertSame('Attendance record updated successfully', $response['message']);
        $this->assertSame('absent', $this->attendanceModel->findResult[13]['status']);
    }

    public function testDeleteNotFound()
    {
        $response = $this->controller->delete(20);
        $this->assertSame(ResponseInterface::HTTP_NOT_FOUND, $response['code']);
    }

    public function testDeleteSuccess()
    {
        $this->attendanceModel->findResult[21] = ['id' => 21];
        $response = $this->controller->delete(21);
        $this->assertSame('Attendance record deleted successfully', $response['message']);
    }

    public function testStudentEndpoint()
    {
        $this->attendanceModel->findAllResult = [['id' => 5]];
        $response = $this->controller->student(5);
        $this->assertSame('Student attendance retrieved successfully', $response['message']);
    }

    public function testClassRosterAggregatesAttendance()
    {
        $stubRows = [
            ['student_id' => 1, 'firstname' => 'A', 'lastname' => 'B'],
            ['student_id' => 2, 'firstname' => 'C', 'lastname' => 'D'],
        ];
        $db = new StubDbConnection($stubRows);
        $this->controller->setDatabaseConnection($db);
        $this->controller->setRequest(new FakeAttendanceRequest(null, ['date' => '2024-10-05']));
        $this->attendanceModel->setAttendanceLookup(1, ['status' => 'present', 'reason' => '']);
        $this->attendanceModel->setAttendanceLookup(2, []);
        $response = $this->controller->classRoster(3);

        $this->assertSame('Class roster attendance retrieved', $response['message']);
        $this->assertSame(2, count($response['data']['students']));
    }

    public function testRecordClassValidationFails()
    {
        $this->controller->setRequest(new FakeAttendanceRequest(['records' => []]));
        $response = $this->controller->recordClass(1);
        $this->assertSame(422, $response['code']);
    }

    public function testRecordClassSavesRows()
    {
        $this->studentClassModel->grade = '5';
        $this->attendanceModel->setAttendanceLookup(7, ['id' => 99]);
        $payload = [
            'date' => '2024-10-01',
            'records' => [
                ['student_id' => 7, 'status' => 'present'],
                ['student_id' => 8, 'status' => 'late'],
            ],
        ];
        $this->controller->setRequest(new FakeAttendanceRequest($payload));
        $response = $this->controller->recordClass(12);

        $this->assertSame('2 attendance row(s) saved', $response['message']);
        $this->assertSame(2, $response['data']['saved']);
    }
}
