<?php

app_require('routes/InvoicingPeriodRoute.php');

use routes\InvoicingPeriodRoute;

function invoicing_period_invoice_state_invoke(string $method, array $args = []): mixed
{
    $reflection = new ReflectionClass(InvoicingPeriodRoute::class);
    $target = $reflection->getMethod($method);
    return $target->invokeArgs(null, $args);
}

final class InvoicingPeriodInvoiceStateFakeResult
{
    public int $num_rows;

    public function __construct(private array $rows)
    {
        $this->num_rows = count($rows);
    }

    public function fetch_assoc(): ?array
    {
        return array_shift($this->rows);
    }
}

final class InvoicingPeriodInvoiceStateFakeDb
{
    public int $queryCount = 0;
    public string $sql = '';

    public function __construct(private array $rows)
    {
    }

    public function query(string $sql): InvoicingPeriodInvoiceStateFakeResult
    {
        $this->queryCount++;
        $this->sql = $sql;
        return new InvoicingPeriodInvoiceStateFakeResult($this->rows);
    }
}

it('applies canonical invoice collection state precedence', function (): void {
    expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[
        'booked_invoice_id' => 9001,
        'processor' => 1,
        'external_id' => 'draft-1',
        'closed_at' => '2026-06-30 12:00:00',
    ]]))->toBe('economic_booked');

    expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[
        'booked_invoice_id' => null,
        'processor' => 1,
        'external_id' => 'draft-2',
        'error_message' => null,
        'closed_at' => '2026-06-30 12:00:00',
    ]]))->toBe('economic_draft');

    expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[
        'processor' => 2,
        'external_id' => 'stripe-id',
        'closed_at' => '2026-06-30 12:00:00',
    ]]))->toBe('closed');

    expect(invoicing_period_invoice_state_invoke('periodInvoiceCollectionState', [[]]))->toBe('open');
});

it('adds transaction states and collection summaries without losing logical orders', function (): void {
    $customer = [
        'customer_number' => 42424242,
        'transactions' => [
            ['id' => 11, 'amount' => 100.5, 'invoice_collection_id' => 501, 'booked' => false],
            ['id' => 12, 'amount' => 50.0, 'invoice_collection_id' => 501, 'booked' => false],
            ['id' => 13, 'amount' => 25.0, 'invoice_collection_id' => 502, 'booked' => false],
            ['id' => 14, 'amount' => 10.0, 'invoice_collection_id' => null, 'booked' => false, 'completed_at' => '2026-06-30 10:00:00'],
        ],
        'queue' => ['invoice_collection_ids' => []],
        'draft' => ['invoice_collection_ids' => [503]],
    ];
    $metadata = [
        501 => ['id' => 501, 'invoice_collection_id' => 501, 'customer_number' => 42424242, 'state' => 'economic_booked'],
        502 => ['id' => 502, 'invoice_collection_id' => 502, 'customer_number' => 42424242, 'state' => 'closed'],
        503 => ['id' => 503, 'invoice_collection_id' => 503, 'customer_number' => 42424242, 'state' => 'economic_draft'],
    ];

    $result = invoicing_period_invoice_state_invoke('applyPeriodInvoiceStateOverlayToCustomer', [$customer, $metadata]);

    expect(array_column($result['transactions'], 'invoice_state'))->toBe([
        'economic_booked',
        'economic_booked',
        'closed',
        'closed',
    ]);
    expect(array_column($result['transactions'], 'booked'))->toBe([true, true, false, false]);
    expect($result['invoice_collections'])->toHaveCount(3);
    expect($result['invoice_collections'][0])->toMatchArray([
        'id' => 501,
        'order_ids' => [11, 12],
        'order_count' => 2,
        'total_net_amount' => 150.5,
    ]);
    expect($result['invoice_collections'][2])->toMatchArray([
        'id' => 503,
        'order_ids' => [],
        'order_count' => 0,
        'total_net_amount' => 0.0,
    ]);
});

it('loads all referenced invoice collection metadata in one local query', function (): void {
    $hadDb = array_key_exists('db', $GLOBALS);
    $originalDb = $GLOBALS['db'] ?? null;
    $fakeDb = new InvoicingPeriodInvoiceStateFakeDb([
        [
            'id' => '501',
            'customer_number' => '42424242',
            'processor' => '1',
            'external_id' => 'draft-501',
            'booked_invoice_id' => null,
            'error_message' => null,
            'closed_at' => '2026-06-30 12:00:00',
        ],
        [
            'id' => '502',
            'customer_number' => '42424242',
            'processor' => '1',
            'external_id' => 'draft-502',
            'booked_invoice_id' => '7002',
            'error_message' => null,
            'closed_at' => '2026-06-30 12:00:00',
        ],
    ]);
    $GLOBALS['db'] = $fakeDb;

    try {
        $metadata = invoicing_period_invoice_state_invoke('getPeriodInvoiceCollectionMetadata', [[
            'all' => [[
                'customer_number' => 42424242,
                'transactions' => [
                    ['invoice_collection_id' => 501],
                    ['invoice_collection_id' => 501],
                ],
                'queue' => ['invoice_collection_ids' => [502]],
                'draft' => ['invoice_collection_ids' => []],
            ]],
            'fixed_pricing' => [[
                'customer_number' => 42424242,
                'transactions' => [['invoice_collection_id' => 502]],
            ]],
        ]]);
    } finally {
        if ($hadDb) {
            $GLOBALS['db'] = $originalDb;
        } else {
            unset($GLOBALS['db']);
        }
    }

    expect($fakeDb->queryCount)->toBe(1);
    expect($fakeDb->sql)->toContain('WHERE id IN (501,502)');
    expect($metadata[501]['state'])->toBe('economic_draft');
    expect($metadata[502]['state'])->toBe('economic_booked');
});
