<?php

namespace Tests\App\Controllers\Api;

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

class StubPreferencesModel
{
    private ?array $firstResult = null;
    private array $findResults = [];
    private int $nextInsertId = 200;
    public ?array $lastUpdate = null;
    public ?array $lastInsertData = null;

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

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

    public function setFirstResult(?array $result): void
    {
        $this->firstResult = $result;
    }

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

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

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

    public function setFindResult(int $id, array $result): void
    {
        $this->findResults[$id] = $result;
    }
}

class FakeJsonRequest extends MockIncomingRequest
{
    public function __construct(private readonly ?array $payload)
    {
        parent::__construct(config(App::class), new URI('https://test.alrahmaisgl.org'), null, new UserAgent());
    }

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

class TestablePreferencesController extends PreferencesController
{
    private ?object $currentUser = null;

    public function __construct()
    {
        // Skip the real constructor to avoid bootstrapping models.
    }

    public function setPreferencesModel(StubPreferencesModel $model): void
    {
        $this->preferencesModel = $model;
    }

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

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

class PreferencesControllerTest extends CIUnitTestCase
{
    private TestablePreferencesController $controller;
    private StubPreferencesModel $preferencesModel;

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

        Services::resetSingle('validation');
        Services::resetSingle('session');

        $this->preferencesModel = new StubPreferencesModel();

        $this->controller = new TestablePreferencesController();
        $this->controller->setPreferencesModel($this->preferencesModel);
    }

    public function testIndexRequiresAuthentication()
    {
        $this->controller->setCurrentUser(null);

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

        $this->assertFalse($response['status']);
        $this->assertSame(401, $response['code']);
        $this->assertSame('Authentication required', $response['message']);
    }

    public function testIndexReturnsDefaultsIfNoPreferences()
    {
        $this->controller->setCurrentUser((object) ['id' => 7]);
        $this->preferencesModel->setFirstResult(null);

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

        $this->assertTrue($response['status']);
        $this->assertSame('Preferences retrieved successfully', $response['message']);
        $this->assertSame(1, $response['data']['notification_email']);
        $this->assertSame('light', $response['data']['theme']);
        $this->assertSame('blue', $response['data']['style_color']);
        $this->assertSame('white', $response['data']['menu_color']);
    }

    public function testIndexPropagatesStoredPreferencesAndSessionStyles()
    {
        $this->controller->setCurrentUser((object) ['id' => 9]);
        $this->preferencesModel->setFirstResult([
            'id' => 11,
            'user_id' => 9,
            'notification_email' => 0,
            'notification_sms' => 0,
            'theme' => 'dark',
            'language' => 'fr',
        ]);

        session()->set('style_color', 'rose');
        session()->set('menu_color', 'navy');

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

        $this->assertTrue($response['status']);
        $this->assertSame(0, $response['data']['notification_email']);
        $this->assertSame('rose', $response['data']['style_color']);
        $this->assertSame('navy', $response['data']['menu_color']);
    }

    public function testUpdateReturnsValidationErrors()
    {
        $this->controller->setCurrentUser((object) ['id' => 3]);
        $this->setRequestPayload(['notification_email' => '2']);

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

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

    public function testUpdateInsertsWhenPreferencesAbsent()
    {
        $this->controller->setCurrentUser((object) ['id' => 12]);
        $this->preferencesModel->setFirstResult(null);

        $payload = [
            'notification_email' => 0,
            'notification_sms' => 1,
            'theme' => 'dark',
            'language' => 'es',
            'style_color' => 'green',
            'menu_color' => 'brand',
        ];

        $this->setRequestPayload($payload);

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

        $this->assertTrue($response['status']);
        $this->assertSame('Preferences updated successfully', $response['message']);
        $this->assertSame(0, $response['data']['notification_email']);
        $this->assertSame('green', session()->get('style_color'));
        $this->assertSame('brand', session()->get('menu_color'));
        $this->assertSame($this->preferencesModel->lastInsertData, [
            'user_id' => 12,
            'notification_email' => 0,
            'notification_sms' => 1,
            'theme' => 'dark',
            'language' => 'es',
        ]);
    }

    public function testUpdateModifiesExistingPreferences()
    {
        $this->controller->setCurrentUser((object) ['id' => 21]);
        $this->preferencesModel->setFirstResult([
            'id' => 18,
            'user_id' => 21,
            'notification_email' => 1,
            'notification_sms' => 1,
            'theme' => 'light',
            'language' => 'en',
        ]);
        $this->preferencesModel->setFindResult(18, [
            'id' => 18,
            'user_id' => 21,
            'notification_email' => 1,
            'notification_sms' => 1,
            'theme' => 'light',
            'language' => 'en',
        ]);

        $payload = ['notification_email' => 1, 'notification_sms' => 0, 'language' => 'fr'];
        $this->setRequestPayload($payload);

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

        $this->assertTrue($response['status']);
        $this->assertSame('Preferences updated successfully', $response['message']);
        $this->assertSame(1, $this->preferencesModel->lastUpdate['data']['notification_email']);
        $this->assertSame(0, $this->preferencesModel->lastUpdate['data']['notification_sms']);
        $this->assertSame(18, $this->preferencesModel->lastUpdate['id']);
        $this->assertSame('fr', $response['data']['language']);
    }

    private function setRequestPayload(?array $payload): void
    {
        $request = new FakeJsonRequest($payload);
        $this->controller->setRequest($request);
    }
}
