<?php

declare(strict_types=1);

usesApiSuite();

function monthly_split_order_collection_id(int $orderId): int
{
    $row = api_test_runtime()->queryOne('SELECT invoice_collection_id FROM orders WHERE id = ' . $orderId . ' LIMIT 1');
    return (int)($row['invoice_collection_id'] ?? 0);
}

function monthly_split_invoice_row(int $invoiceCollectionId): array
{
    return api_test_runtime()->queryOne('SELECT id, created_at, closed_at FROM collected_order_invoices WHERE id = ' . $invoiceCollectionId . ' LIMIT 1') ?? [];
}

function monthly_split_customer_collection_count(int $customerNumber): int
{
    $row = api_test_runtime()->queryOne('SELECT COUNT(*) AS count FROM collected_order_invoices WHERE customer_number = ' . $customerNumber);
    return (int)($row['count'] ?? 0);
}

function monthly_split_cleanup_collections(array $invoiceCollectionIds): void
{
    $ids = array_values(array_unique(array_filter(array_map('intval', $invoiceCollectionIds), static fn(int $id): bool => $id > 0)));
    if ($ids === []) {
        return;
    }

    api_test_runtime()->db()->query('UPDATE orders SET invoice_collection_id = NULL WHERE invoice_collection_id IN (' . implode(',', $ids) . ')');
    api_test_runtime()->db()->query('DELETE FROM collected_order_invoices WHERE id IN (' . implode(',', $ids) . ')');
}

it('previews monthly split changes without moving orders or creating collections', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'preview');

    $customer = api_fixtures()->createUser(['display_name' => 'Preview Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $invoiceCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
    ]);
    $marchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-03-15 10:00:00',
    ]);
    $aprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-04-02 10:00:00',
    ]);
    $session = api_fixtures()->createUserSession(['split_collected_invoice']);
    $collectionCountBefore = monthly_split_customer_collection_count((int)$customer['customer_number']);

    $response = api_client()->post('/collected-invoices/split-by-month', [
        'dateFrom' => '2096-03-01',
        'dateTo' => '2096-04-30',
        'preview' => true,
    ], $session['headers']);

    $response
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    $payload = $response->data();
    $months = (array)($payload['changed'][0]['months'] ?? []);
    expect($payload['preview'] ?? null)->toBeTrue()
        ->and($payload['processed_count'] ?? null)->toBe(1)
        ->and($payload['changed_count'] ?? null)->toBe(1)
        ->and($payload['changed'][0]['created_invoice_collection_ids'] ?? null)->toBe([])
        ->and($months[0]['month'] ?? null)->toBe('2096-03')
        ->and($months[0]['will_create_collection'] ?? null)->toBeFalse()
        ->and($months[0]['target_invoice_collection_id'] ?? null)->toBe((int)$invoiceCollection['id'])
        ->and($months[0]['closed_at'] ?? null)->toBeNull()
        ->and($months[1]['month'] ?? null)->toBe('2096-04')
        ->and($months[1]['will_create_collection'] ?? null)->toBeTrue()
        ->and($months[1]['target_invoice_collection_id'] ?? null)->toBeNull()
        ->and($months[1]['closed_at'] ?? null)->toBeNull()
        ->and(monthly_split_customer_collection_count((int)$customer['customer_number']))->toBe($collectionCountBefore)
        ->and(monthly_split_order_collection_id((int)$marchOrder['id']))->toBe((int)$invoiceCollection['id'])
        ->and(monthly_split_order_collection_id((int)$aprilOrder['id']))->toBe((int)$invoiceCollection['id']);
});

it('previews only explicit monthly split invoice collection ids', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'preview-scope');

    $customer = api_fixtures()->createUser(['display_name' => 'Scoped Preview Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $targetCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
    ]);
    $ignoredCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
    ]);
    $targetMarchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $targetCollection['id'],
        'created_at' => '2096-03-15 10:00:00',
    ]);
    $targetAprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $targetCollection['id'],
        'created_at' => '2096-04-02 10:00:00',
    ]);
    $ignoredMarchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $ignoredCollection['id'],
        'created_at' => '2096-03-16 10:00:00',
    ]);
    $ignoredAprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $ignoredCollection['id'],
        'created_at' => '2096-04-03 10:00:00',
    ]);
    $session = api_fixtures()->createUserSession(['split_collected_invoice']);

    $response = api_client()->post('/collected-invoices/split-by-month', [
        'dateFrom' => '2096-03-01',
        'dateTo' => '2096-04-30',
        'invoice_collection_ids' => [$targetCollection['id']],
        'preview' => true,
    ], $session['headers']);

    $response
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    $payload = $response->data();
    expect($payload['processed_count'] ?? null)->toBe(1)
        ->and($payload['changed_count'] ?? null)->toBe(1)
        ->and($payload['changed'][0]['invoice_collection_id'] ?? null)->toBe((int)$targetCollection['id'])
        ->and(monthly_split_order_collection_id((int)$targetMarchOrder['id']))->toBe((int)$targetCollection['id'])
        ->and(monthly_split_order_collection_id((int)$targetAprilOrder['id']))->toBe((int)$targetCollection['id'])
        ->and(monthly_split_order_collection_id((int)$ignoredMarchOrder['id']))->toBe((int)$ignoredCollection['id'])
        ->and(monthly_split_order_collection_id((int)$ignoredAprilOrder['id']))->toBe((int)$ignoredCollection['id']);
});

it('splits a selected March and April collected invoice into monthly collections', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'happy');

    $customer = api_fixtures()->createUser(['display_name' => 'Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $invoiceCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
        'created_at' => '2096-03-01 00:00:01',
    ]);
    $marchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-03-15 10:00:00',
    ]);
    $aprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-04-02 10:00:00',
    ]);
    $session = api_fixtures()->createUserSession(['split_collected_invoice']);
    $createdCollectionIds = [];

    try {
        $response = api_client()->post('/collected-invoices/split-by-month', [
            'dateFrom' => '2096-03-01',
            'dateTo' => '2096-04-30',
        ], $session['headers']);

        $response
            ->assertStatus(200)
            ->assertEnvelope()
            ->assertSuccess();

        $payload = $response->data();
        $createdCollectionIds = (array)($payload['changed'][0]['created_invoice_collection_ids'] ?? []);
        $aprilCollectionId = (int)($createdCollectionIds[0] ?? 0);

        expect($payload['processed_count'] ?? null)->toBe(1)
            ->and($payload['changed_count'] ?? null)->toBe(1)
            ->and($payload['skipped_count'] ?? null)->toBe(0)
            ->and($aprilCollectionId)->toBeGreaterThan(0)
            ->and(monthly_split_order_collection_id((int)$marchOrder['id']))->toBe((int)$invoiceCollection['id'])
            ->and(monthly_split_order_collection_id((int)$aprilOrder['id']))->toBe($aprilCollectionId)
            ->and(monthly_split_invoice_row((int)$invoiceCollection['id'])['created_at'] ?? null)->toBe('2096-03-01 00:00:01')
            ->and(monthly_split_invoice_row((int)$invoiceCollection['id'])['closed_at'] ?? null)->toBeNull()
            ->and(monthly_split_invoice_row($aprilCollectionId)['created_at'] ?? null)->toBe('2096-04-01 00:00:01')
            ->and(monthly_split_invoice_row($aprilCollectionId)['closed_at'] ?? null)->toBeNull();
    } finally {
        monthly_split_cleanup_collections($createdCollectionIds);
    }
});

it('splits only explicit monthly split invoice collection ids', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'scope');

    $customer = api_fixtures()->createUser(['display_name' => 'Scoped Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $targetCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
        'created_at' => '2096-03-01 00:00:01',
    ]);
    $ignoredCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
        'created_at' => '2096-03-01 00:00:01',
    ]);
    $targetMarchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $targetCollection['id'],
        'created_at' => '2096-03-15 10:00:00',
    ]);
    $targetAprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $targetCollection['id'],
        'created_at' => '2096-04-02 10:00:00',
    ]);
    $ignoredMarchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $ignoredCollection['id'],
        'created_at' => '2096-03-16 10:00:00',
    ]);
    $ignoredAprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $ignoredCollection['id'],
        'created_at' => '2096-04-03 10:00:00',
    ]);
    $session = api_fixtures()->createUserSession(['split_collected_invoice']);
    $createdCollectionIds = [];

    try {
        $response = api_client()->post('/collected-invoices/split-by-month', [
            'dateFrom' => '2096-03-01',
            'dateTo' => '2096-04-30',
            'invoice_collection_ids' => [$targetCollection['id']],
        ], $session['headers']);

        $response
            ->assertStatus(200)
            ->assertEnvelope()
            ->assertSuccess();

        $payload = $response->data();
        $createdCollectionIds = (array)($payload['changed'][0]['created_invoice_collection_ids'] ?? []);
        $aprilCollectionId = (int)($createdCollectionIds[0] ?? 0);

        expect($payload['processed_count'] ?? null)->toBe(1)
            ->and($payload['changed_count'] ?? null)->toBe(1)
            ->and($aprilCollectionId)->toBeGreaterThan(0)
            ->and(monthly_split_order_collection_id((int)$targetMarchOrder['id']))->toBe((int)$targetCollection['id'])
            ->and(monthly_split_order_collection_id((int)$targetAprilOrder['id']))->toBe($aprilCollectionId)
            ->and(monthly_split_order_collection_id((int)$ignoredMarchOrder['id']))->toBe((int)$ignoredCollection['id'])
            ->and(monthly_split_order_collection_id((int)$ignoredAprilOrder['id']))->toBe((int)$ignoredCollection['id']);
    } finally {
        monthly_split_cleanup_collections($createdCollectionIds);
    }
});

it('sets closed_at to month end when split month has ended', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'closed-at');

    $customer = api_fixtures()->createUser(['display_name' => 'Ended Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $invoiceCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
        'created_at' => '2001-03-01 00:00:01',
    ]);
    $marchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2001-03-15 10:00:00',
    ]);
    $aprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2001-04-02 10:00:00',
    ]);
    $session = api_fixtures()->createUserSession(['split_collected_invoice']);
    $createdCollectionIds = [];

    try {
        $response = api_client()->post('/collected-invoices/split-by-month', [
            'dateFrom' => '2001-03-01',
            'dateTo' => '2001-04-30',
        ], $session['headers']);

        $response
            ->assertStatus(200)
            ->assertEnvelope()
            ->assertSuccess();

        $payload = $response->data();
        $createdCollectionIds = (array)($payload['changed'][0]['created_invoice_collection_ids'] ?? []);
        $aprilCollectionId = (int)($createdCollectionIds[0] ?? 0);

        expect($payload['changed_count'] ?? null)->toBe(1)
            ->and(monthly_split_order_collection_id((int)$marchOrder['id']))->toBe((int)$invoiceCollection['id'])
            ->and(monthly_split_order_collection_id((int)$aprilOrder['id']))->toBe($aprilCollectionId)
            ->and(monthly_split_invoice_row((int)$invoiceCollection['id'])['closed_at'] ?? null)->toBe('2001-03-31 23:59:59')
            ->and(monthly_split_invoice_row($aprilCollectionId)['closed_at'] ?? null)->toBe('2001-04-30 23:59:59');
    } finally {
        monthly_split_cleanup_collections($createdCollectionIds);
    }
});

it('splits the whole affected collection even when only one month is selected', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'partial');

    $customer = api_fixtures()->createUser(['display_name' => 'Partial Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $invoiceCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
    ]);
    $marchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-03-20 09:00:00',
    ]);
    $aprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-04-10 09:00:00',
    ]);
    $session = api_fixtures()->createUserSession(['split_collected_invoice']);
    $createdCollectionIds = [];

    try {
        $response = api_client()->post('/collected-invoices/split-by-month', [
            'dateFrom' => '2096-03-01',
            'dateTo' => '2096-03-31',
        ], $session['headers']);

        $response
            ->assertStatus(200)
            ->assertEnvelope()
            ->assertSuccess();

        $payload = $response->data();
        $createdCollectionIds = (array)($payload['changed'][0]['created_invoice_collection_ids'] ?? []);
        $aprilCollectionId = (int)($createdCollectionIds[0] ?? 0);

        expect($payload['changed_count'] ?? null)->toBe(1)
            ->and(monthly_split_order_collection_id((int)$marchOrder['id']))->toBe((int)$invoiceCollection['id'])
            ->and(monthly_split_order_collection_id((int)$aprilOrder['id']))->toBe($aprilCollectionId);
    } finally {
        monthly_split_cleanup_collections($createdCollectionIds);
    }
});

it('does not affect booked collected invoices', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'booked-skip');

    $customer = api_fixtures()->createUser(['display_name' => 'Booked Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $invoiceCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
        'booked_invoice_id' => 987654,
    ]);
    $marchOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-03-05 12:00:00',
    ]);
    $aprilOrder = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $invoiceCollection['id'],
        'created_at' => '2096-04-05 12:00:00',
    ]);
    $session = api_fixtures()->createUserSession(['split_collected_invoice']);

    $response = api_client()->post('/collected-invoices/split-by-month', [
        'dateFrom' => '2096-03-01',
        'dateTo' => '2096-03-31',
    ], $session['headers']);

    $response
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    $payload = $response->data();
    expect($payload['changed_count'] ?? null)->toBe(0)
        ->and($payload['skipped_count'] ?? null)->toBe(1)
        ->and((string)($payload['skipped'][0]['message'] ?? ''))->toContain('booked')
        ->and(monthly_split_order_collection_id((int)$marchOrder['id']))->toBe((int)$invoiceCollection['id'])
        ->and(monthly_split_order_collection_id((int)$aprilOrder['id']))->toBe((int)$invoiceCollection['id']);
});

it('skips draft linked, Stripe, and single month collections', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'skip');

    $customer = api_fixtures()->createUser(['display_name' => 'Skipped Monthly Split Customer']);
    $department = api_fixtures()->createDepartment();
    $draftCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
        'external_id' => 'draft-external-reference',
    ]);
    $stripeCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
        'processor' => 2,
    ]);
    $singleMonthCollection = api_fixtures()->createInvoiceCollection([
        'customer_number' => $customer['customer_number'],
    ]);

    foreach ([$draftCollection, $stripeCollection] as $collection) {
        api_fixtures()->createOrder([
            'customer_id' => $customer['customer_number'],
            'department_id' => $department['id'],
            'invoice_collection_id' => $collection['id'],
            'created_at' => '2096-03-05 12:00:00',
        ]);
        api_fixtures()->createOrder([
            'customer_id' => $customer['customer_number'],
            'department_id' => $department['id'],
            'invoice_collection_id' => $collection['id'],
            'created_at' => '2096-04-05 12:00:00',
        ]);
    }

    api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'invoice_collection_id' => $singleMonthCollection['id'],
        'created_at' => '2096-03-10 12:00:00',
    ]);

    $session = api_fixtures()->createUserSession(['split_collected_invoice']);

    $response = api_client()->post('/collected-invoices/split-by-month', [
        'dateFrom' => '2096-03-01',
        'dateTo' => '2096-03-31',
    ], $session['headers']);

    $response
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess();

    $payload = $response->data();
    expect($payload['processed_count'] ?? null)->toBe(3)
        ->and($payload['changed_count'] ?? null)->toBe(0)
        ->and($payload['skipped_count'] ?? null)->toBe(3);
});

it('rejects invalid monthly split date ranges', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'failure');

    $session = api_fixtures()->createUserSession(['split_collected_invoice']);

    api_client()->post('/collected-invoices/split-by-month', [
        'dateFrom' => '2096-04-30',
        'dateTo' => '2096-03-01',
    ], $session['headers'])
        ->assertStatus(400)
        ->assertEnvelope()
        ->assertSuccess(false);
});

it('rejects invalid explicit monthly split invoice collection ids', function (): void {
    api_test_covers('POST /collected-invoices/split-by-month', 'invalid-scope');

    $session = api_fixtures()->createUserSession(['split_collected_invoice']);

    api_client()->post('/collected-invoices/split-by-month', [
        'dateFrom' => '2096-03-01',
        'dateTo' => '2096-04-30',
        'invoice_collection_ids' => ['not-a-number'],
    ], $session['headers'])
        ->assertStatus(400)
        ->assertEnvelope()
        ->assertSuccess(false);

    api_client()->post('/collected-invoices/split-by-month', [
        'dateFrom' => '2096-03-01',
        'dateTo' => '2096-04-30',
        'invoice_collection_ids' => [],
    ], $session['headers'])
        ->assertStatus(400)
        ->assertEnvelope()
        ->assertSuccess(false);
});
