<?php

namespace Tests\App\Controllers\Api;

use App\Controllers\Api\AssignmentController;
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 FakeJsonRequest extends MockIncomingRequest
{
    public function __construct(private readonly ?array $payload)
    {
        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->payload;
    }
}

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

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

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

class AssignmentStubPager
{
    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 StubAssignmentModel
{
    public array $whereHistory = [];
    public array $saved = [];
    public int $nextId = 10;
    public array $findAllResult = [];
    public array $findResult = [];
    public array $pagerData = ['current' => 1, 'perPage' => 20, 'total' => 0];

    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 AssignmentStubPager($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->nextId++;
        $this->saved[$this->nextId] = $data;
        $this->findResult[$this->nextId] = ['id' => $this->nextId] + $data;
        return $this->nextId;
    }

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

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

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

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

class TestableAssignmentController extends AssignmentController
{
    public bool $validationResult = true;
    public array $validationErrors = [];
    public $validator;

    public function __construct()
    {
        // skip real initialization
    }

    public function setAssignmentModel(StubAssignmentModel $model): void
    {
        $this->assignmentModel = $model;
    }

    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 respond($data = null, ?int $status = null, string $message = '')
    {
        return ['data' => $data, 'code' => $status, 'message' => $message];
    }
}

class AssignmentControllerTest extends CIUnitTestCase
{
    private TestableAssignmentController $controller;
    private StubAssignmentModel $model;

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

        $this->controller = new TestableAssignmentController();
        $this->model = new StubAssignmentModel();
        $this->controller->setAssignmentModel($this->model);
    }

    public function testIndexAppliesFilters()
    {
        $request = new FakeGetRequest(['page' => '2', 'per_page' => '5', 'class_id' => '12', 'teacher_id' => '3', 'student_id' => '7']);
        $this->controller->setRequest($request);
        $this->model->findAllResult = [['id' => 1]];
        $this->model->pagerData = ['current' => 2, 'perPage' => 5, 'total' => 11];

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

        $this->assertSame('Assignments retrieved successfully', $response['message']);
        $this->assertSame(['id' => 1], $response['data']['data'][0]);
        $this->assertSame(2, $response['data']['pagination']['current_page']);
        $this->assertCount(3, $this->model->whereHistory);
    }

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

    public function testShowSuccess()
    {
        $this->model->findResult[5] = ['id' => 5];
        $response = $this->controller->show(5);
        $this->assertSame('Assignment retrieved successfully', $response['message']);
        $this->assertSame(['id' => 5], $response['data']);
    }

    public function testStoreValidationFails()
    {
        $this->controller->setValidationResult(false, ['title' => 'required']);
        $request = new FakeJsonRequest(['title' => '']);
        $this->controller->setRequest($request);

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

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

    public function testStoreCreatesAssignment()
    {
        $request = new FakeJsonRequest([
            'title' => 'Test',
            'description' => '',
            'due_date' => '2025-01-01',
            'class_id' => 1,
            'teacher_id' => 2,
        ]);
        $this->controller->setRequest($request);
        $this->controller->setValidationResult(true);

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

        $this->assertSame('Assignment created successfully', $response['message']);
        $this->assertSame('Test', $this->model->saved[$this->model->getInsertID()]['title']);
    }

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

    public function testUpdateModifiesAssignment()
    {
        $this->model->findResult[15] = ['id' => 15, 'title' => 'Old'];
        $request = new FakeJsonRequest(['title' => 'New']);
        $this->controller->setRequest($request);
        $this->controller->setValidationResult(true);

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

        $this->assertSame('Assignment updated successfully', $response['message']);
        $this->assertSame('New', $this->model->findResult[15]['title']);
    }

    public function testDeleteFlow()
    {
        $this->model->findResult[20] = ['id' => 20];
        $response = $this->controller->delete(20);
        $this->assertSame('Assignment deleted successfully', $response['message']);
        $this->assertArrayNotHasKey(20, $this->model->findResult);
    }

    public function testStudentReturnsList()
    {
        $this->model->findAllResult = [['id' => 1]];
        $response = $this->controller->student(4);
        $this->assertSame('Assignments retrieved successfully for student', $response['message']);
        $this->assertNotEmpty($response['data']);
    }
}
