<?php

use App\Models\CategoryModel;
use App\Services\CategoryService;
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 CategoryFlowTest extends CIUnitTestCase
{
    use DatabaseTestTrait;
    use FeatureTestTrait;
    use AuthTestTrait;
    use CategoryTestTrait;

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

    public function testUnauthenticatedUserCannotAccessCategoryManager(): void
    {
        $this->withSession([])->get('categories')->assertRedirectTo('/sign-in');
        $this->withSession([])->post('categories', ['name' => 'Nope'])->assertRedirectTo('/sign-in');
    }

    public function testUserSeesOnlyOwnCategories(): void
    {
        $alice = $this->createUser('list-alice@example.com', 1);
        $bob   = $this->createUser('list-bob@example.com', 1);
        $this->createCategory((int) $alice['id'], 'Alice Only');
        $this->createCategory((int) $bob['id'], 'Secret Bob');

        $page = $this->withSession($this->authSession($alice))->get('categories');
        $page->assertOK();
        $page->assertSee('Alice Only');
        $page->assertDontSee('Secret Bob');
        $page->assertSee('Add New Category');
        $page->assertDontSee('Secret Bob');
    }

    public function testAddEditAndEmptyDeleteOverHttp(): void
    {
        $user = $this->createUser('http-cat@example.com', 1);

        $this->withSession($this->authSession($user))
            ->post('categories', ['name' => '  Errands  '])
            ->assertRedirectTo('/categories');
        $this->seeInDatabase('categories', ['user_id' => $user['id'], 'name' => 'Errands']);

        $this->withSession($this->authSession($user))
            ->post('categories', ['name' => ''])
            ->assertSee(CategoryService::MSG_REQUIRED);
        $this->withSession($this->authSession($user))
            ->post('categories', ['name' => '   '])
            ->assertSee(CategoryService::MSG_REQUIRED);
        $this->withSession($this->authSession($user))
            ->post('categories', ['name' => 'errands'])
            ->assertSee(CategoryService::MSG_DUPLICATE);

        $row = (new CategoryModel())->where('user_id', $user['id'])->where('name', 'Errands')->first();
        $this->assertIsArray($row);

        $this->withSession($this->authSession($user))
            ->post('categories/' . $row['id'], ['name' => 'Errands'])
            ->assertRedirectTo('/categories');

        $this->withSession($this->authSession($user))
            ->post('categories/' . $row['id'] . '/delete')
            ->assertRedirectTo('/categories');
        $this->assertNull((new CategoryModel())->find($row['id']));
    }

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

        $edit = $this->withSession($this->authSession($alice))->post('categories/' . $bobs['id'], ['name' => 'Hacked']);
        $edit->assertSee(CategoryService::MSG_NOT_FOUND);
        $edit->assertDontSee('Bob Private');

        $this->withSession($this->authSession($alice))
            ->post('categories/' . $bobs['id'] . '/delete')
            ->assertRedirectTo('/categories');

        $this->seeInDatabase('categories', ['id' => $bobs['id'], 'name' => 'Bob Private', 'deleted_at' => null]);
    }

    public function testDeleteWithTasksDoesNotSilentlyProceed(): void
    {
        $user = $this->createUser('silent@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Busy');
        $this->createTask((int) $user['id'], (int) $cat['id']);

        $this->withSession($this->authSession($user))
            ->post('categories/' . $cat['id'] . '/delete')
            ->assertRedirectTo('/categories');
        $this->assertNotNull((new CategoryModel())->find($cat['id']));
    }

    public function testMoveAndDeleteOverHttp(): void
    {
        $user   = $this->createUser('http-move@example.com', 1);
        $source = $this->createCategory((int) $user['id'], 'From');
        $target = $this->createCategory((int) $user['id'], 'To');
        $task   = $this->createTask((int) $user['id'], (int) $source['id']);
        $this->createRecurrence((int) $task['id']);
        $this->createOccurrence((int) $task['id']);

        $this->withSession($this->authSession($user))
            ->post('categories/' . $source['id'] . '/move-and-delete', [
                'target_category_id' => (string) $target['id'],
            ])
            ->assertRedirectTo('/categories');

        $this->seeInDatabase('tasks', ['id' => $task['id'], 'category_id' => $target['id']]);
        $this->assertNull((new CategoryModel())->find($source['id']));
        $this->assertSame(1, db_connect()->table('task_recurrence_rules')->where('task_id', $task['id'])->countAllResults());
    }

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

        $this->withSession($this->authSession($user))
            ->post('categories/' . $cat['id'] . '/delete-with-tasks')
            ->assertRedirectTo('/categories');

        $this->assertNull((new CategoryModel())->find($cat['id']));
        $deleted = db_connect()->table('tasks')->where('id', $task['id'])->get()->getRowArray();
        $this->assertNotNull($deleted['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());
    }

    public function testNoDestructiveActionThroughGet(): void
    {
        $user = $this->createUser('get-del@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Stay');

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

        $this->expectException(PageNotFoundException::class);
        $this->withSession($this->authSession($user))->get('categories/' . $cat['id'] . '/delete');
    }

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