<?php

use App\Models\CategoryModel;
use App\Services\CategoryService;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Tests\Support\Auth\AuthTestTrait;
use Tests\Support\Categories\CategoryTestTrait;

/**
 * @internal
 */
final class CategoryServiceTest extends CIUnitTestCase
{
    use DatabaseTestTrait;
    use AuthTestTrait;
    use CategoryTestTrait;

    protected $namespace = 'App';
    protected $refresh   = true;
    protected $migrate   = true;
    protected $seed      = '';

    private CategoryService $categories;

    protected function setUp(): void
    {
        parent::setUp();
        $this->categories = new CategoryService();
    }

    public function testCreateRejectsBlankWhitespaceDuplicateAndAllowsOtherUsers(): void
    {
        $alice = $this->createUser('cat-alice@example.com', 1);
        $bob   = $this->createUser('cat-bob@example.com', 1);

        $this->assertFalse($this->categories->create((int) $alice['id'], '')['ok']);
        $this->assertFalse($this->categories->create((int) $alice['id'], '   ')['ok']);
        $this->assertSame(CategoryService::MSG_LENGTH, $this->categories->create((int) $alice['id'], str_repeat('c', 101))['message']);

        $created = $this->categories->create((int) $alice['id'], ' Personal ');
        $this->assertTrue($created['ok']);
        $this->assertSame('Personal', $created['category']['name']);

        $dup = $this->categories->create((int) $alice['id'], 'personal');
        $this->assertFalse($dup['ok']);
        $this->assertSame(CategoryService::MSG_DUPLICATE, $dup['message']);

        $bobCopy = $this->categories->create((int) $bob['id'], 'Personal');
        $this->assertTrue($bobCopy['ok']);
    }

    public function testUpdateOwnNameAllowedAndDuplicateRejected(): void
    {
        $user = $this->createUser('cat-edit@example.com', 1);
        $one  = $this->createCategory((int) $user['id'], 'Home');
        $two  = $this->createCategory((int) $user['id'], 'Office');

        $same = $this->categories->update((int) $user['id'], (int) $one['id'], 'Home');
        $this->assertTrue($same['ok']);

        $clash = $this->categories->update((int) $user['id'], (int) $one['id'], 'office');
        $this->assertFalse($clash['ok']);
        $this->assertSame(CategoryService::MSG_DUPLICATE, $clash['message']);

        $renamed = $this->categories->update((int) $user['id'], (int) $one['id'], 'Lifestyle');
        $this->assertTrue($renamed['ok']);
        $this->seeInDatabase('categories', ['id' => $one['id'], 'name' => 'Lifestyle']);
        $this->assertNotNull((new CategoryModel())->find($two['id']));
    }

    public function testMoveAndDeleteMovesTasksAndKeepsRecurrenceRowsValid(): void
    {
        $user   = $this->createUser('cat-move@example.com', 1);
        $source = $this->createCategory((int) $user['id'], 'Source');
        $target = $this->createCategory((int) $user['id'], 'Target');
        $task   = $this->createTask((int) $user['id'], (int) $source['id'], 'Recurring', ['is_recurring' => 1]);
        $ruleId = $this->createRecurrence((int) $task['id']);
        $occId  = $this->createOccurrence((int) $task['id']);

        $result = $this->categories->moveAndDelete((int) $user['id'], (int) $source['id'], (int) $target['id']);
        $this->assertTrue($result['ok']);

        $this->seeInDatabase('tasks', ['id' => $task['id'], 'category_id' => $target['id']]);
        $this->seeInDatabase('task_recurrence_rules', ['id' => $ruleId, 'task_id' => $task['id']]);
        $this->seeInDatabase('task_occurrences', ['id' => $occId, 'task_id' => $task['id']]);
        $this->assertNull((new CategoryModel())->find($source['id']));
        $this->assertNotNull((new CategoryModel())->find($target['id']));
    }

    public function testDeleteWithTasksRemovesRecurrenceWithoutOrphans(): void
    {
        $user = $this->createUser('cat-purge@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Doomed');
        $task = $this->createTask((int) $user['id'], (int) $cat['id'], 'Gone', ['is_recurring' => 1]);
        $this->createRecurrence((int) $task['id']);
        $this->createOccurrence((int) $task['id']);

        $result = $this->categories->deleteWithTasks((int) $user['id'], (int) $cat['id']);
        $this->assertTrue($result['ok']);

        $taskRow = db_connect()->table('tasks')->where('id', $task['id'])->get()->getRowArray();
        $this->assertNotNull($taskRow['deleted_at']);
        $this->assertSame(0, db_connect()->table('task_recurrence_rules')->where('task_id', $task['id'])->countAllResults());
        $this->assertSame(0, db_connect()->table('task_occurrences')->where('task_id', $task['id'])->countAllResults());
        $this->assertNull((new CategoryModel())->find($cat['id']));

        $orphans = db_connect()->query(
            'SELECT r.id FROM task_recurrence_rules r LEFT JOIN tasks t ON t.id = r.task_id WHERE t.id IS NULL',
        )->getResultArray();
        $this->assertSame([], $orphans);
    }

    public function testTransactionRollbackLeavesDataUnchanged(): void
    {
        $user   = $this->createUser('cat-rollback@example.com', 1);
        $source = $this->createCategory((int) $user['id'], 'Alpha');
        $target = $this->createCategory((int) $user['id'], 'Beta');
        $task   = $this->createTask((int) $user['id'], (int) $source['id']);

        $this->categories->beforeCommit = static function (): void {
            throw new RuntimeException('forced failure');
        };

        $result = $this->categories->moveAndDelete((int) $user['id'], (int) $source['id'], (int) $target['id']);
        $this->assertFalse($result['ok']);
        $this->assertSame(CategoryService::MSG_FAIL, $result['message']);
        $this->seeInDatabase('tasks', ['id' => $task['id'], 'category_id' => $source['id']]);
        $this->assertNotNull((new CategoryModel())->find($source['id']));
        $this->assertNotNull((new CategoryModel())->find($target['id']));
    }

    public function testMoveRejectsInvalidTargets(): void
    {
        $alice = $this->createUser('cat-target-a@example.com', 1);
        $bob   = $this->createUser('cat-target-b@example.com', 1);
        $src   = $this->createCategory((int) $alice['id'], 'Only');
        $this->createTask((int) $alice['id'], (int) $src['id']);
        $bobCat = $this->createCategory((int) $bob['id'], 'BobCat');

        $this->assertSame(
            CategoryService::MSG_BAD_TARGET,
            $this->categories->moveAndDelete((int) $alice['id'], (int) $src['id'], (int) $src['id'] + 999)['message'],
        );

        $second = $this->createCategory((int) $alice['id'], 'Second');
        $this->assertSame(
            CategoryService::MSG_SAME,
            $this->categories->moveAndDelete((int) $alice['id'], (int) $src['id'], (int) $src['id'])['message'],
        );
        $this->assertSame(
            CategoryService::MSG_BAD_TARGET,
            $this->categories->moveAndDelete((int) $alice['id'], (int) $src['id'], (int) $bobCat['id'])['message'],
        );
        $this->assertSame(
            CategoryService::MSG_NO_TARGET,
            $this->categories->moveAndDelete((int) $alice['id'], (int) $src['id'], 0)['message'],
        );
        $this->assertNotNull((new CategoryModel())->find($second['id']));
    }
}
