#!/usr/bin/env php
<?php

declare(strict_types=1);

use RedMCP\ListQueryNormalizer;

require __DIR__ . '/../vendor/autoload.php';

final class QueryNormalizerTest
{
    private int $assertions = 0;

    public function run(): void
    {
        $this->testDefaultPagingAndRawOverride();
        $this->testIssueFriendlyFilters();
        $this->testSortShortcutsAndStructuredSort();
        $this->testDatePresetsAndRanges();
        $this->testUserParams();
        $this->testSearchParams();

        fwrite(STDOUT, "OK {$this->assertions} assertions\n");
    }

    private function testDefaultPagingAndRawOverride(): void
    {
        $params = ListQueryNormalizer::listParams([
            'limit' => 500,
            'page' => 3,
            'params' => ['limit' => 75],
        ]);

        $this->assertSame(75, $params['limit'], 'raw params override normalized limit');
        $this->assertSame(200, $params['offset'], 'page offset uses clamped normalized limit before raw override');
    }

    private function testIssueFriendlyFilters(): void
    {
        $filters = ListQueryNormalizer::issueFilters([
            'project_id' => 'customer-service',
            'status' => 'open',
            'assigned_to_id' => 25,
            'updated' => 'since 2026-04-01',
            'filters' => ['assigned_to_id' => 'me'],
        ]);

        $this->assertSame('customer-service', $filters['project_id'], 'project_id is copied');
        $this->assertSame('open', $filters['status_id'], 'open status alias maps to Redmine open status');
        $this->assertSame('me', $filters['assigned_to_id'], 'raw filters override normalized filters');
        $this->assertSame('>=2026-04-01', $filters['updated_on'], 'since date maps to Redmine lower bound');
    }

    private function testSortShortcutsAndStructuredSort(): void
    {
        $newest = ListQueryNormalizer::listParams(['sort' => 'newest']);
        $this->assertSame('updated_on:desc', $newest['sort'], 'newest shortcut sorts by updated_on descending');

        $priority = ListQueryNormalizer::listParams(['sort' => 'priority']);
        $this->assertSame('priority:desc,updated_on:desc', $priority['sort'], 'priority shortcut includes updated_on tie-breaker');

        $structured = ListQueryNormalizer::listParams([
            'sort' => [
                ['field' => 'created_on', 'direction' => 'desc'],
                ['field' => 'id', 'direction' => 'asc'],
            ],
        ]);
        $this->assertSame('created_on:desc,id:asc', $structured['sort'], 'structured sort converts to Redmine sort string');
    }

    private function testDatePresetsAndRanges(): void
    {
        $clock = new DateTimeImmutable('2026-04-25 12:00:00', new DateTimeZone('UTC'));

        $today = ListQueryNormalizer::issueFilters(['created' => 'today'], $clock);
        $this->assertSame('2026-04-25', $today['created_on'], 'today maps to exact date');

        $range = ListQueryNormalizer::issueFilters(['created' => '2026-04-01..2026-04-25'], $clock);
        $this->assertSame('><2026-04-01|2026-04-25', $range['created_on'], 'range string maps to Redmine between syntax');

        $objectRange = ListQueryNormalizer::issueFilters(['due' => ['from' => '2026-05-01', 'to' => '2026-05-31']], $clock);
        $this->assertSame('><2026-05-01|2026-05-31', $objectRange['due_date'], 'object range maps to due_date');

        $lastSeven = ListQueryNormalizer::issueFilters(['updated' => 'last 7 days'], $clock);
        $this->assertSame('><2026-04-19|2026-04-25', $lastSeven['updated_on'], 'last N days includes today');

        $freeText = ListQueryNormalizer::issueFilters(['created' => 'April 2 2026'], $clock);
        $this->assertSame('2026-04-02', $freeText['created_on'], 'simple free text date parses');
    }

    private function testSearchParams(): void
    {
        $params = ListQueryNormalizer::searchParams([
            'project_id' => 'customer-service',
            'all_words' => true,
            'titles_only' => false,
            'open_issues' => true,
            'sort' => 'oldest',
            'page' => 2,
            'params' => ['offset' => 5],
        ]);

        $this->assertSame('customer-service', $params['project_id'], 'search project_id is copied');
        $this->assertSame('1', $params['all_words'], 'true search flags map to Redmine 1');
        $this->assertSame('0', $params['titles_only'], 'false search flags map to Redmine 0');
        $this->assertSame('1', $params['open_issues'], 'open_issues flag maps to Redmine 1');
        $this->assertSame('created_on:asc', $params['sort'], 'oldest shortcut maps to created_on ascending');
        $this->assertSame(5, $params['offset'], 'raw search params override normalized paging');
    }

    private function testUserParams(): void
    {
        $params = ListQueryNormalizer::userParams([
            'status' => 'active',
            'name' => 'danny',
            'group_id' => 4,
            'sort' => 'oldest',
        ]);

        $this->assertSame(1, $params['status'], 'active user status maps to Redmine active status id');
        $this->assertSame('danny', $params['name'], 'user name filter is copied');
        $this->assertSame(4, $params['group_id'], 'user group filter is copied');
        $this->assertSame('created_on:asc', $params['sort'], 'user list accepts sort shortcuts');
    }

    /**
     * @param mixed $expected
     * @param mixed $actual
     */
    private function assertSame($expected, $actual, string $message): void
    {
        $this->assertions++;
        if ($expected === $actual) {
            return;
        }

        fwrite(STDERR, "FAIL: {$message}\nExpected: " . var_export($expected, true) . "\nActual: " . var_export($actual, true) . "\n");
        exit(1);
    }
}

(new QueryNormalizerTest())->run();
