<?php

namespace Tests\App\Controllers\Api;

use App\Controllers\Api\ScoreCommentController;
use CodeIgniter\HTTP\URI;
use CodeIgniter\HTTP\UserAgent;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockIncomingRequest;
use Config\App;
use Config\Services;

class FakeScoreRequest extends MockIncomingRequest
{
    public function __construct(private readonly ?array $json, 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 StubScoreCommentModel
{
    private array $existingQueue = [];
    public array $savedRecords = [];
    public array $lastUpdate = [];
    private int $lastInsert = 100;
    public array $findAllResult = [];

    public function where($column, $value = null): self
    {
        return $this;
    }

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

    public function addExisting(array $row): void
    {
        $this->existingQueue[] = $row;
    }

    public function insert(array $data): int
    {
        $this->lastInsert++;
        $id = $this->lastInsert;
        $this->savedRecords[$id] = ['id' => $id] + $data;
        return $id;
    }

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

    public function find(int $id): ?array
    {
        return $this->savedRecords[$id] ?? null;
    }

    public function setFind(int $id, array $row): void
    {
        $this->savedRecords[$id] = $row;
    }

    public function setFindAllResult(array $result): void
    {
        $this->findAllResult = $result;
    }

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

class TestableScoreCommentController extends ScoreCommentController
{
    private ?object $currentUser = null;

    public function __construct()
    {
        // bypass real constructor logic.
    }

    public function setScoreCommentModel(StubScoreCommentModel $model): void
    {
        $this->scoreCommentModel = $model;
    }

    public function setCurrentUser(?object $user): void
    {
        $this->currentUser = $user;
    }

    public function setAcademicPeriod(string $schoolYear, string $semester): void
    {
        $this->schoolYear = $schoolYear;
        $this->semester = $semester;
    }

    protected function getCurrentUser(): ?object
    {
        return $this->currentUser;
    }
}

class ScoreCommentControllerTest extends CIUnitTestCase
{
    private TestableScoreCommentController $controller;
    private StubScoreCommentModel $model;

    protected function setUp(): void
    {
        parent::setUp();
        Services::resetSingle('validation');

        $this->controller = new TestableScoreCommentController();
        $this->model = new StubScoreCommentModel();
        $this->controller->setScoreCommentModel($this->model);
        $this->controller->setAcademicPeriod('2024-2025', 'Spring');
    }

    public function testSaveReturnsErrorWhenNoData()
    {
        $request = new FakeScoreRequest(null);
        $this->controller->setRequest($request);
        $this->controller->setCurrentUser((object) ['id' => 4]);

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

        $this->assertFalse($response['status']);
        $this->assertSame(400, $response['code']);
        $this->assertSame('Invalid request data', $response['message']);
    }

    public function testSaveInsertsCommentsSkippingEmpty()
    {
        $request = new FakeScoreRequest([
            'subject_id' => 7,
            'comments' => [
                101 => ['Exam' => 'Well done', 'Project' => '   '],
            ],
        ]);
        $this->controller->setRequest($request);
        $this->controller->setCurrentUser((object) ['id' => 22]);

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

        $this->assertTrue($response['status']);
        $this->assertSame('Comments saved successfully', $response['message']);
        $this->assertCount(1, $response['data']);
        $saved = $response['data'][0];
        $this->assertSame('Well done', $saved['comment']);
        $this->assertSame('exam', $saved['score_type']);
    }

    public function testSaveUpdatesExistingComment()
    {
        $existing = [
            'id' => 55,
            'student_id' => 201,
            'score_type' => 'Exam',
        ];
        $this->model->addExisting($existing);
        $this->model->setFind(55, ['id' => 55, 'comment' => 'Old']);

        $request = new FakeScoreRequest([
            'subject_id' => 7,
            'comments' => [
                201 => ['Exam' => 'Better now'],
            ],
            'semester' => 'Fall',
        ]);

        $this->controller->setRequest($request);
        $this->controller->setCurrentUser((object) ['id' => 9]);

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

        $this->assertTrue($response['status']);
        $this->assertSame(55, $this->model->lastUpdate['id']);
        $this->assertSame('Better now', $this->model->lastUpdate['data']['comment']);
        $this->assertSame('Fall', $this->model->lastUpdate['data']['semester']);
    }

    public function testGetByStudentReturnsComments()
    {
        $this->controller->setRequest(new FakeScoreRequest(null, ['subject_id' => 5, 'score_type' => 'exam']));
        $this->model->setFindAllResult([['id' => 1, 'student_id' => 7]]);

        $response = $this->controller->getByStudent(7);

        $this->assertTrue($response['status']);
        $this->assertCount(1, $response['data']);
        $this->assertSame('Comments retrieved successfully', $response['message']);
    }

    public function testUpdateReturnsNotFoundWhenMissing()
    {
        $this->model->setFindAllResult([]);
        $request = new FakeScoreRequest([]);
        $this->controller->setRequest($request);

        $response = $this->controller->update(99);

        $this->assertFalse($response['status']);
        $this->assertSame(404, $response['code']);
    }

    public function testUpdateModifiesComment()
    {
        $this->model->setFind(42, ['id' => 42, 'comment' => 'Old']);
        $request = new FakeScoreRequest(['comment' => 'Fresh']);
        $this->controller->setRequest($request);

        $response = $this->controller->update(42);

        $this->assertTrue($response['status']);
        $this->assertSame('Fresh', $this->model->lastUpdate['data']['comment']);
        $this->assertSame('Comment updated successfully', $response['message']);
    }
}
