<?php

use App\Models\TaskModel;
use App\Services\TaskService;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
use Config\Filters;
use Config\Security;
use Tests\Support\Auth\AuthTestTrait;
use Tests\Support\Categories\CategoryTestTrait;

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

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

    /**
     * @param array<string, string> $override
     *
     * @return array<string, string>
     */
    private function form(int $categoryId, array $override = []): array
    {
        return array_merge([
            'title'       => 'Buy milk',
            'priority'    => '1',
            'category_id' => (string) $categoryId,
            'due_date'    => '',
            'due_time'    => '',
        ], $override);
    }

    public function testUnauthenticatedCreateAndEditRedirectToSignIn(): void
    {
        $this->withSession([])->get('tasks/create')->assertRedirectTo('/sign-in');
        $this->withSession([])->post('tasks/create', $this->form(1))->assertRedirectTo('/sign-in');
        $this->withSession([])->get('tasks/1/edit')->assertRedirectTo('/sign-in');
        $this->withSession([])->post('tasks/1/edit', $this->form(1))->assertRedirectTo('/sign-in');
    }

    public function testCreateFormShowsOnlyCurrentUsersActiveCategories(): void
    {
        $alice = $this->createUser('flow-alice@example.com', 1);
        $bob   = $this->createUser('flow-bob@example.com', 1);
        $this->createCategory((int) $alice['id'], 'Alice Only');
        $this->createCategory((int) $bob['id'], 'Secret Bob');
        db_connect()->table('categories')->insert([
            'user_id'    => $alice['id'],
            'name'       => 'Inactive Alice',
            'is_active'  => 0,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);

        $page = $this->withSession($this->authSession($alice))->get('tasks/create');
        $page->assertOK();
        $page->assertSee('Alice Only');
        $page->assertDontSee('Secret Bob');
        $page->assertDontSee('Inactive Alice');
        $page->assertSee('Add Task');
        $this->assertStringContainsString('Save &amp; Continue', (string) $page->response()->getBody());
        $page->assertDontSee("blank date = today");
        $page->assertDontSee('23:59');
    }

    public function testNoCategoriesShowsPathToCategoryManager(): void
    {
        $user = $this->createUser('flow-none@example.com', 1);
        $page = $this->withSession($this->authSession($user))->get('tasks/create');
        $page->assertOK();
        $page->assertSee(TaskService::MSG_NO_CATEGORIES);
        $page->assertSee('Go to Category Manager');
        $this->assertStringNotContainsString('Save &amp; Continue', (string) $page->response()->getBody());

        $this->withSession($this->authSession($user))
            ->post('tasks/create', $this->form(1, ['title' => 'Nope']))
            ->assertSee(TaskService::MSG_NO_CATEGORIES);
        $this->assertSame(0, db_connect()->table('tasks')->where('user_id', $user['id'])->countAllResults());
    }

    public function testCreateValidationRejectsBlankTitlePriorityCategoryAndCrossUserCategory(): void
    {
        $alice = $this->createUser('flow-val-a@example.com', 1);
        $bob   = $this->createUser('flow-val-b@example.com', 1);
        $aliceCat = $this->createCategory((int) $alice['id'], 'Alice Home');
        $bobCat   = $this->createCategory((int) $bob['id'], 'Bob Home');
        $session  = $this->authSession($alice);

        $blank = $this->withSession($session)->post('tasks/create', $this->form((int) $aliceCat['id'], [
            'title' => '',
        ]));
        $blank->assertSee(TaskService::MSG_TITLE_REQUIRED);

        $space = $this->withSession($session)->post('tasks/create', $this->form((int) $aliceCat['id'], [
            'title' => '   ',
        ]));
        $space->assertSee(TaskService::MSG_TITLE_REQUIRED);
        $space->assertSee('value="   "');

        $priority = $this->withSession($session)->post('tasks/create', $this->form((int) $aliceCat['id'], [
            'priority' => '9',
        ]));
        $priority->assertSee(TaskService::MSG_PRIORITY);

        $missing = $this->withSession($session)->post('tasks/create', $this->form((int) $aliceCat['id'], [
            'category_id' => '',
        ]));
        $missing->assertSee(TaskService::MSG_CATEGORY);

        $foreign = $this->withSession($session)->post('tasks/create', $this->form((int) $bobCat['id']));
        $foreign->assertSee(TaskService::MSG_CATEGORY);

        $this->assertSame(0, db_connect()->table('tasks')->where('user_id', $alice['id'])->countAllResults());
    }

    public function testValidCreateUsesSessionUserAndIgnoresPostedUserId(): void
    {
        $alice = $this->createUser('flow-create@example.com', 1);
        $bob   = $this->createUser('flow-create-bob@example.com', 1);
        $cat   = $this->createCategory((int) $alice['id'], 'Home');

        $this->withSession($this->authSession($alice))
            ->post('tasks/create', $this->form((int) $cat['id'], [
                'title'    => '  Walk the dog  ',
                'user_id'  => (string) $bob['id'],
                'due_date' => '',
                'due_time' => '18:00',
            ]))
            ->assertRedirectTo('/dashboard');

        $task = (new TaskModel())->where('user_id', $alice['id'])->where('title', 'Walk the dog')->first();
        $this->assertIsArray($task);
        $this->assertSame((int) $alice['id'], (int) $task['user_id']);
        $this->assertNotSame((int) $bob['id'], (int) $task['user_id']);
        $this->assertNull($task['due_date']);
        $this->assertNull($task['due_time']);
        $this->assertSame(1, db_connect()->table('task_occurrences')->where('task_id', $task['id'])->countAllResults());
    }

    public function testInvalidDateTimeAndRecurringAreRejectedOverHttp(): void
    {
        $user = $this->createUser('flow-dt@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Home');
        $session = $this->authSession($user);

        $this->withSession($session)
            ->post('tasks/create', $this->form((int) $cat['id'], ['due_date' => '2026-02-30']))
            ->assertSee(TaskService::MSG_DATE);

        $this->withSession($session)
            ->post('tasks/create', $this->form((int) $cat['id'], [
                'due_date' => '2026-09-02',
                'due_time' => '25:00',
            ]))
            ->assertSee(TaskService::MSG_TIME);

        $recurring = $this->withSession($session)->post('tasks/create', $this->form((int) $cat['id'], [
            'is_recurring' => '1',
        ]));
        $recurring->assertSee(\App\Services\RecurrenceService::MSG_START);
        $recurring->assertSee('checked');

        $this->assertSame(0, db_connect()->table('tasks')->where('user_id', $user['id'])->countAllResults());
        $this->assertSame(0, db_connect()->table('task_recurrence_rules')->countAllResults());
    }

    public function testPriorityQueryPreselectsOnlyValuesOneThroughFour(): void
    {
        $user = $this->createUser('flow-preselect@example.com', 1);
        $this->createCategory((int) $user['id'], 'Home');
        $session = $this->authSession($user);

        $ok = $this->withSession($session)->get('tasks/create', ['priority' => '3']);
        $ok->assertOK();
        $this->assertMatchesRegularExpression(
            '/name="priority" value="3"[^>]*checked/',
            (string) $ok->response()->getBody(),
        );

        foreach (['0', '5', '99', 'abc'] as $invalid) {
            $page = $this->withSession($session)->get('tasks/create', ['priority' => $invalid]);
            $page->assertOK();
            $this->assertDoesNotMatchRegularExpression(
                '/name="priority" value="[1-4]"[^>]*checked/',
                (string) $page->response()->getBody(),
            );
        }

        $plain = $this->withSession($session)->get('tasks/create');
        $this->assertDoesNotMatchRegularExpression(
            '/name="priority" value="[1-4]"[^>]*checked/',
            (string) $plain->response()->getBody(),
        );
    }

    public function testEditFormLoadsExistingValuesAndUpdatesOccurrence(): void
    {
        $user = $this->createUser('flow-edit@example.com', 1);
        $home = $this->createCategory((int) $user['id'], 'Home');
        $work = $this->createCategory((int) $user['id'], 'Office');
        $session = $this->authSession($user);

        $this->withSession($session)
            ->post('tasks/create', $this->form((int) $home['id'], [
                'title'    => 'Original title',
                'priority' => '1',
                'due_date' => '2026-09-02',
                'due_time' => '09:15',
            ]))
            ->assertRedirectTo('/dashboard');

        $task = (new TaskModel())->where('user_id', $user['id'])->where('title', 'Original title')->first();
        $this->assertIsArray($task);

        $form = $this->withSession($session)->get('tasks/' . $task['id'] . '/edit');
        $form->assertOK();
        $form->assertSee('Edit Task');
        $form->assertSee('Original title');
        $form->assertSee('value="2026-09-02"');
        $form->assertSee('value="09:15"');
        $this->assertMatchesRegularExpression(
            '/name="priority" value="1"[^>]*checked/',
            (string) $form->response()->getBody(),
        );

        $this->withSession($session)
            ->post('tasks/' . $task['id'] . '/edit', $this->form((int) $work['id'], [
                'title'    => 'Updated title',
                'priority' => '4',
                'due_date' => '2026-09-05',
                'due_time' => '16:40',
            ]))
            ->assertRedirectTo('/dashboard');

        $this->seeInDatabase('tasks', [
            'id'          => $task['id'],
            'title'       => 'Updated title',
            'priority'    => 4,
            'category_id' => $work['id'],
            'due_date'    => '2026-09-05',
        ]);
        $this->assertSame(1, db_connect()->table('task_occurrences')->where('task_id', $task['id'])->countAllResults());
        $occurrence = db_connect()->table('task_occurrences')->where('task_id', $task['id'])->get()->getRowArray();
        $this->assertSame('2026-09-05', $occurrence['occurrence_date']);
        $this->assertSame('16:40:00', $occurrence['occurrence_time']);
    }

    public function testUserCannotViewOrUpdateAnotherUsersTask(): void
    {
        $alice = $this->createUser('flow-own-a@example.com', 1);
        $bob   = $this->createUser('flow-own-b@example.com', 1);
        $aliceCat = $this->createCategory((int) $alice['id'], 'Alice');
        $bobCat   = $this->createCategory((int) $bob['id'], 'Bob');

        $this->withSession($this->authSession($bob))
            ->post('tasks/create', $this->form((int) $bobCat['id'], ['title' => 'Secret Bob Task']))
            ->assertRedirectTo('/dashboard');
        $bobs = (new TaskModel())->where('user_id', $bob['id'])->first();
        $this->assertIsArray($bobs);

        $get = $this->withSession($this->authSession($alice))->get('tasks/' . $bobs['id'] . '/edit');
        $get->assertRedirectTo('/dashboard');
        $this->assertStringNotContainsString('Secret Bob Task', (string) $get->response()->getBody());

        $this->withSession($this->authSession($alice))
            ->post('tasks/' . $bobs['id'] . '/edit', $this->form((int) $aliceCat['id'], [
                'title' => 'Hacked',
            ]))
            ->assertRedirectTo('/dashboard');

        $this->seeInDatabase('tasks', ['id' => $bobs['id'], 'title' => 'Secret Bob Task', 'user_id' => $bob['id']]);
    }

    public function testEditCannotSwitchToAnotherUsersCategory(): void
    {
        $alice = $this->createUser('flow-switch-a@example.com', 1);
        $bob   = $this->createUser('flow-switch-b@example.com', 1);
        $aliceCat = $this->createCategory((int) $alice['id'], 'Alice');
        $bobCat   = $this->createCategory((int) $bob['id'], 'Bob');

        $this->withSession($this->authSession($alice))
            ->post('tasks/create', $this->form((int) $aliceCat['id'], ['title' => 'Stay put']))
            ->assertRedirectTo('/dashboard');
        $task = (new TaskModel())->where('user_id', $alice['id'])->first();

        $this->withSession($this->authSession($alice))
            ->post('tasks/' . $task['id'] . '/edit', $this->form((int) $bobCat['id'], [
                'title' => 'Stay put',
            ]))
            ->assertSee(TaskService::MSG_CATEGORY);

        $this->seeInDatabase('tasks', ['id' => $task['id'], 'category_id' => $aliceCat['id']]);
    }

    public function testGetDoesNotCreateOrMutateTasks(): void
    {
        $user = $this->createUser('flow-get@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Home');
        $session = $this->authSession($user);

        $this->withSession($session)->get('tasks/create', [
            'title'       => 'Hacked',
            'priority'    => '1',
            'category_id' => (string) $cat['id'],
        ]);
        $this->assertSame(0, db_connect()->table('tasks')->where('user_id', $user['id'])->countAllResults());

        $this->withSession($session)
            ->post('tasks/create', $this->form((int) $cat['id'], ['title' => 'Keep me']))
            ->assertRedirectTo('/dashboard');
        $task = (new TaskModel())->where('user_id', $user['id'])->first();

        $this->withSession($session)->get('tasks/' . $task['id'] . '/edit', [
            'title'       => 'Changed',
            'priority'    => '4',
            'category_id' => (string) $cat['id'],
        ]);
        $this->seeInDatabase('tasks', ['id' => $task['id'], 'title' => 'Keep me', 'priority' => 1]);

        $routes = (string) file_get_contents(APPPATH . 'Config/Routes.php');
        $this->assertStringContainsString("post('tasks/create'", $routes);
        $this->assertStringContainsString("post('tasks/(:num)/edit'", $routes);
        $this->assertStringNotContainsString("get('tasks/(:num)/delete'", $routes);

        $this->expectException(PageNotFoundException::class);
        $this->withSession($session)->get('tasks/' . $task['id'] . '/delete');
    }

    public function testCsrfStillConfiguredOutsideTesting(): void
    {
        $source = (string) file_get_contents(APPPATH . 'Config/Filters.php');
        $this->assertStringContainsString("'csrf'", $source);
        $this->assertStringContainsString("ENVIRONMENT === 'testing'", $source);
        $this->assertSame('cookie', (new Security())->csrfProtection);
        $this->assertSame('testing', ENVIRONMENT);
        $this->assertNotContains('csrf', (new Filters())->globals['before']);
    }
}
