<?php

namespace Tests\App\Controllers\Api;

use App\Controllers\Api\MessagesController;
use App\Models\MessageModel as BaseMessageModel;
use App\Models\StudentClassModel as BaseStudentClassModel;
use App\Models\StudentModel as BaseStudentModel;
use App\Models\TeacherClassModel as BaseTeacherClassModel;
use App\Models\UserModel as BaseUserModel;
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 FakeMessagesRequest extends MockIncomingRequest
{
    public function __construct(private readonly ?array $json = null)
    {
        parent::__construct(config(App::class), new URI('http://example.com'), 'php://input', new UserAgent());
    }

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

    public function getPost($key = null, $filter = null, $default = null)
    {
        if ($key === null) {
            return $this->json ?? [];
        }

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

class StubMessageModel extends BaseMessageModel
{
    public array $inbox = [];
    public array $sent = [];
    public array $drafts = [];
    public array $trashed = [];
    public array $inserted = [];
    public array $marked = [];

    public function __construct()
    {
        // avoid parent initialization
    }

    public function getInboxMessages($userId)
    {
        return $this->inbox;
    }

    public function getSentMessages($userId)
    {
        return $this->sent;
    }

    public function getDraftMessages($userId)
    {
        return $this->drafts;
    }

    public function getTrashedMessages($userId)
    {
        return $this->trashed;
    }

    public function insert($row = null, bool $returnID = true)
    {
        $this->inserted = is_array($row) ? $row : [];
        return 123;
    }

    public function find($id = null)
    {
        return ['id' => $id ?? 0] + $this->inserted;
    }

    public function markAsRead($messageId)
    {
        $this->marked[] = $messageId;
    }
}

class StubTeacherClassModel extends BaseTeacherClassModel
{
    public array $data = [];

    public function findAll($limit = 0, $offset = 0)
    {
        return $this->data;
    }
}

class StubStudentClassModel extends BaseStudentClassModel
{
    public array $perClass = [];
    private $lastClass = null;

    public function where($column, $value)
    {
        if ($column === 'class_section_id') {
            $this->lastClass = $value;
        }
        return $this;
    }

    public function findAll($limit = 0, $offset = 0)
    {
        return $this->perClass[$this->lastClass] ?? [];
    }
}

class StubStudentModel extends BaseStudentModel
{
    public array $students = [];

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

class StubUserModel extends BaseUserModel
{
    public array $users = [];

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

class TestableMessagesController extends MessagesController
{
    public StubMessageModel $stubMessageModel;
    public StubTeacherClassModel $stubTeacherClassModel;
    public StubStudentClassModel $stubStudentClassModel;
    public StubStudentModel $stubStudentModel;
    public StubUserModel $stubUserModel;
    public array $receivedMessages = [];
    public array $read = [];

    public function __construct()
    {
        // intentionally skip parent constructor
    }

    protected function success($data = null, string $message = 'Success', int $code = ResponseInterface::HTTP_OK)
    {
        return ['data' => $data, 'message' => $message, 'code' => $code];
    }

    protected function error(string $message = 'An error occurred', int $code = ResponseInterface::HTTP_BAD_REQUEST, ?array $errors = null)
    {
        return ['message' => $message, 'code' => $code, 'errors' => $errors];
    }

    protected function getCurrentUser(): ?object
    {
        return (object)[
            'id' => 42,
            'roles' => ['administrator'],
        ];
    }

    protected function getReceivedMessages(int $userId): array
    {
        return $this->receivedMessages;
    }

    protected function markAsRead(int $messageId): void
    {
        $this->read[] = $messageId;
    }

    protected function generateMessageNumber(): string
    {
        return 'MSG-TEST';
    }

    protected function getRecipientId(array $role): ?int
    {
        return 99;
    }

    public function setModels()
    {
        $this->messageModel = new StubMessageModel();
        $this->teacherClassModel = new StubTeacherClassModel();
        $this->studentClassModel = new StubStudentClassModel();
        $this->studentModel = new StubStudentModel();
        $this->userModel = new StubUserModel();
    }
}

class MessagesControllerTest extends CIUnitTestCase
{
    private TestableMessagesController $controller;

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

        $this->controller = new TestableMessagesController();
        $this->controller->setModels();
    }

    public function testInboxUsesMessageModel()
    {
        $this->controller->messageModel->inbox = [['id' => 1]];

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

        $this->assertSame(ResponseInterface::HTTP_OK, $response['code']);
        $this->assertSame([['id' => 1]], $response['data']);
    }

    public function testSendCreatesMessageWhenRecipientProvided()
    {
        $request = new FakeMessagesRequest([
            'recipient_id' => 5,
            'subject' => 'Hi',
            'message' => 'Hello',
        ]);
        $this->controller->setRequest($request);

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

        $this->assertSame(ResponseInterface::HTTP_OK, $response['code']);
        $this->assertSame('Message sent successfully', $response['message']);
        $this->assertSame('MSG-TEST', $response['data']['message_number']);
        $this->assertNotEmpty($this->controller->messageModel->inserted);
    }

    public function testGetRecipientsReturnsTeachers()
    {
        $this->controller->teacherClassModel->data = [
            ['teacher_id' => 12, 'class_section_id' => 1],
        ];
        $this->controller->userModel->users = [
            12 => ['firstname' => 'Jane', 'lastname' => 'Doe'],
        ];

        $response = $this->controller->getRecipients('teacher');

        $this->assertSame(ResponseInterface::HTTP_OK, $response['code']);
        $this->assertCount(1, $response['data']);
        $this->assertSame('Jane Doe', $response['data'][0]['name']);
    }
}
