<?php

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

/**
 * @internal
 */
final class AnalyticsFlowTest 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, mixed>
     */
    private function makeTask(int $userId, int $categoryId, string $title, array $override = []): array
    {
        $result = (new TaskService())->create($userId, (new TaskService())->collect(array_merge([
            'title'       => $title,
            'priority'    => '1',
            'category_id' => (string) $categoryId,
            'due_date'    => '2026-08-29',
            'due_time'    => '',
        ], $override)));
        $this->assertTrue($result['ok'] ?? false, (string) ($result['message'] ?? 'create failed'));

        return $result['task'];
    }

    /**
     * @return array<string, mixed>
     */
    private function chartFromPage(string $body): array
    {
        $matched = preg_match(
            '#<script type="application/json" id="analytics-data">(.+?)</script>#s',
            $body,
            $matches,
        );
        $this->assertSame(1, $matched, 'Analytics JSON payload is missing.');
        $decoded = json_decode($matches[1], true);
        $this->assertIsArray($decoded, 'Analytics chart payload must be valid JSON.');
        $this->assertSame(JSON_ERROR_NONE, json_last_error());

        return $decoded;
    }

    public function testUnauthenticatedAnalyticsRedirectsToSignIn(): void
    {
        $this->withSession([])->get('analytics')->assertRedirectTo('/sign-in');
    }

    public function testEmptyAnalyticsRendersThreeSectionsWithoutSampleData(): void
    {
        $user = $this->createUser('analytics-flow-empty@example.com', 1);
        $page = $this->withSession($this->authSession($user))->get('analytics');
        $page->assertOK();
        $page->assertSee('Analytics');
        $page->assertSee('Completed vs Pending');
        $page->assertSee('Tasks by Priority');
        $page->assertSee('Tasks by Category');
        $page->assertSee(AnalyticsService::EMPTY_MESSAGE);
        $page->assertDontSee('Analytics charts are implemented in Phase 9.');
        $body = (string) $page->response()->getBody();
        $this->assertStringNotContainsString('id="analytics-status-chart"', $body);
        $this->assertStringContainsString('chart.umd.min.js', $body);
        $this->assertStringContainsString('assets/js/analytics.js', $body);
        $chart = $this->chartFromPage($body);
        $this->assertTrue($chart['status']['empty']);
        $this->assertTrue($chart['priority']['empty']);
        $this->assertTrue($chart['category']['empty']);
        $this->assertSame([0, 0], $chart['status']['counts']);
    }

    public function testPageRendersCountsAndDoesNotUseDashboardTodayFilter(): void
    {
        $user = $this->createUser('analytics-flow-scope@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Personal');
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Yesterday', ['due_date' => '2026-08-28']);
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Far future', ['due_date' => '2026-12-31']);
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Undated', ['due_date' => '']);
        $done = $this->makeTask((int) $user['id'], (int) $cat['id'], 'Done today', ['due_date' => date('Y-m-d')]);
        $complete = (new RecurrenceService())->completeOccurrence(
            (int) $user['id'],
            (int) db_connect()->table('task_occurrences')->where('task_id', $done['id'])->get()->getRowArray()['id'],
        );
        $this->assertTrue($complete['ok'] ?? false);

        $page = $this->withSession($this->authSession($user))->get('analytics');
        $page->assertOK();
        $page->assertSee('75%');
        $page->assertSee('25%');
        $page->assertSee('Personal');
        $body  = (string) $page->response()->getBody();
        $chart = $this->chartFromPage($body);
        $this->assertSame([3, 1], $chart['status']['counts']);
        $this->assertFalse($chart['status']['empty']);
        $this->assertContains('Personal', $chart['category']['labels']);
        $this->assertSame([3], $chart['category']['counts']);
    }

    public function testUserIdQueryParameterIsIgnored(): void
    {
        $alice = $this->createUser('analytics-flow-iso-a@example.com', 1);
        $bob   = $this->createUser('analytics-flow-iso-b@example.com', 1);
        $aCat  = $this->createCategory((int) $alice['id'], 'Personal');
        $bCat  = $this->createCategory((int) $bob['id'], 'Personal');
        $this->makeTask((int) $alice['id'], (int) $aCat['id'], 'Shared title');
        $this->makeTask((int) $bob['id'], (int) $bCat['id'], 'Shared title');
        $this->makeTask((int) $bob['id'], (int) $bCat['id'], 'Bob extra', ['due_date' => '2026-08-01']);

        $page = $this->withSession($this->authSession($alice))->get('analytics', ['user_id' => (string) $bob['id']]);
        $page->assertOK();
        $chart = $this->chartFromPage((string) $page->response()->getBody());
        $this->assertSame([1, 0], $chart['status']['counts']);
        $page->assertDontSee('Bob extra');
    }

    public function testPriorityLabelsComeFromTieasyConfig(): void
    {
        $user   = $this->createUser('analytics-flow-labels@example.com', 1);
        $cat    = $this->createCategory((int) $user['id'], 'Work');
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'One of each p1', ['priority' => '1']);
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'One of each p2', ['priority' => '2', 'due_date' => '2026-08-01']);
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'One of each p3', ['priority' => '3', 'due_date' => '2026-08-02']);
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'One of each p4', ['priority' => '4', 'due_date' => '2026-08-03']);

        $page = $this->withSession($this->authSession($user))->get('analytics');
        $page->assertOK();
        $body = (string) $page->response()->getBody();
        foreach ((new Tieasy())->priorities as $label) {
            $this->assertStringContainsString(esc($label), $body);
        }
        $chart = $this->chartFromPage((string) $page->response()->getBody());
        $this->assertSame(array_values((new Tieasy())->priorities), $chart['priority']['labels']);
        $this->assertSame([1, 1, 1, 1], $chart['priority']['counts']);
    }

    public function testCategoryApostropheAndQuotesRenderSafely(): void
    {
        $user = $this->createUser('analytics-flow-xss@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], "Client's Work");
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Quote task');

        $page = $this->withSession($this->authSession($user))->get('analytics');
        $page->assertOK();
        $body = (string) $page->response()->getBody();
        $this->assertStringContainsString('Client&#039;s Work', $body);
        $this->assertDoesNotMatchRegularExpression(
            '#id="analytics-data">[^<]*Client\'s Work#',
            $body,
        );
        $chart = $this->chartFromPage($body);
        $this->assertSame(["Client's Work"], $chart['category']['labels']);
        $this->assertStringContainsString('id="analytics-category-chart"', $body);
    }

    public function testDeletedAndForeignDataDoNotRender(): void
    {
        $alice = $this->createUser('analytics-flow-gone-a@example.com', 1);
        $bob   = $this->createUser('analytics-flow-gone-b@example.com', 1);
        $aCat  = $this->createCategory((int) $alice['id'], 'Keep');
        $bCat  = $this->createCategory((int) $bob['id'], 'Keep');
        $this->makeTask((int) $alice['id'], (int) $aCat['id'], 'Alice visible');
        $this->makeTask((int) $bob['id'], (int) $bCat['id'], 'Bob secret');
        $gone = $this->makeTask((int) $alice['id'], (int) $aCat['id'], 'Soft gone', ['due_date' => '2026-08-01']);
        (new TaskModel())->delete((int) $gone['id']);
        $tomb = $this->makeTask((int) $alice['id'], (int) $aCat['id'], 'Occ gone', ['due_date' => '2026-08-02']);
        db_connect()->table('task_occurrences')
            ->where('task_id', $tomb['id'])
            ->update(['is_deleted' => 1]);

        $page = $this->withSession($this->authSession($alice))->get('analytics');
        $page->assertOK();
        $page->assertDontSee('Bob secret');
        $page->assertDontSee('Soft gone');
        $page->assertDontSee('Occ gone');
        $chart = $this->chartFromPage((string) $page->response()->getBody());
        $this->assertSame([1, 0], $chart['status']['counts']);
    }

    public function testNoUnprotectedAnalyticsJsonEndpoint(): void
    {
        $this->expectException(PageNotFoundException::class);
        $this->withSession([])->get('api/analytics');
    }
}
