<?php

use classes\db;
use classes\economic_transfer_executor;
use classes\economic_transfer_queue;

app_require('classes/economic_transfer_executor.php');

if (!class_exists('EconomicTransferQueueIntegrationStubExecutor')) {
    class EconomicTransferQueueIntegrationStubExecutor extends economic_transfer_executor
    {
        /** @var array<int, bool> */
        private array $fail_once_order_draft = [];

        public function failNextOrderDraft(int $order_id): void
        {
            $this->fail_once_order_draft[$order_id] = true;
        }

        public function exportOrderDraftInvoice(int $order_id, int $user_id = 0): array
        {
            if (($this->fail_once_order_draft[$order_id] ?? false) === true) {
                unset($this->fail_once_order_draft[$order_id]);
                throw new Exception('Simulated draft export failure for order ' . $order_id);
            }

            return [
                'order_id' => $order_id,
                'user_id' => $user_id,
                'mode' => 'draft',
            ];
        }

        public function exportOrderInvoice(int $order_id, int $user_id = 0): array
        {
            return [
                'order_id' => $order_id,
                'user_id' => $user_id,
                'mode' => 'invoice',
            ];
        }

        public function exportCollectedInvoice(int $collected_invoice_id, bool $send_as_is = false, int $user_id = 0): array
        {
            return [
                'collected_invoice_id' => $collected_invoice_id,
                'send_as_is' => $send_as_is,
                'user_id' => $user_id,
                'mode' => 'collected',
            ];
        }
    }
}

function economic_transfer_queue_integration_db(): db
{
    if (!integration_enabled()) {
        test()->markTestSkipped('Set RUN_INTEGRATION_TESTS=1 to run DB integration tests.');
    }

    $host = getenv('CONFIG_DB_HOST') ?: null;
    $user = getenv('CONFIG_DB_USER') ?: null;
    $password = getenv('CONFIG_DB_PASSWORD') ?: '';
    $database = getenv('CONFIG_DB_DATABASE') ?: null;
    $port = (int)(getenv('CONFIG_DB_PORT') ?: 3306);
    if (!$host || !$user || !$database) {
        test()->markTestSkipped('Missing DB env vars: CONFIG_DB_HOST/CONFIG_DB_USER/CONFIG_DB_DATABASE.');
    }

    app_require('classes/db.php');
    app_require('classes/economic_transfer_executor.php');
    app_require('classes/economic_transfer_queue_schema_bootstrap.php');
    app_require('classes/economic_transfer_queue.php');

    $GLOBALS['response'] = new class {
        public function internal_server_error(string $message): void
        {
            throw new RuntimeException($message);
        }
    };

    $db = new db([
        'host' => $host,
        'user' => $user,
        'password' => $password,
        'database' => $database,
        'port' => $port,
    ]);
    try {
        $db->connect();
    } catch (Throwable $throwable) {
        test()->markTestSkipped('Integration DB unavailable: ' . $throwable->getMessage());
    }
    $GLOBALS['db'] = $db;
    return $db;
}

function economic_transfer_queue_cleanup_for_created_by(db $db, int $created_by): void
{
    $db->query("DELETE FROM economic_transfer_queue_jobs WHERE created_by = $created_by");
}

it('processes queued transfer jobs to completion', function (): void {
    $db = economic_transfer_queue_integration_db();
    $created_by = 920000 + random_int(1000, 9999);
    $order_id = 930000 + random_int(1000, 9999);
    $queue = new economic_transfer_queue(new EconomicTransferQueueIntegrationStubExecutor());

    try {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);

        $job = $queue->enqueue(
            economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT,
            [
                'order_id' => $order_id,
                'requested_by' => $created_by,
            ],
            $created_by
        );

        expect((int)$job['id'])->toBeGreaterThan(0);
        expect((string)$job['status'])->toBe(economic_transfer_queue::STATUS_QUEUED);

        $summary = $queue->processPending(1);
        expect((int)$summary['processed'])->toBe(1);
        expect((int)$summary['completed'])->toBe(1);
        expect((int)$summary['failed'])->toBe(0);

        $processed_job = $queue->getJobById((int)$job['id']);
        expect($processed_job)->not->toBeNull();
        expect((string)$processed_job['status'])->toBe(economic_transfer_queue::STATUS_COMPLETED);
        expect((int)($processed_job['result']['order_id'] ?? 0))->toBe($order_id);
    } finally {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);
        $db->close();
    }
});

it('deduplicates active jobs per transfer target', function (): void {
    $db = economic_transfer_queue_integration_db();
    $created_by = 921000 + random_int(1000, 9999);
    $collected_invoice_id = 931000 + random_int(1000, 9999);
    $queue = new economic_transfer_queue(new EconomicTransferQueueIntegrationStubExecutor());

    try {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);

        $first = $queue->enqueue(
            economic_transfer_queue::TYPE_COLLECTED_INVOICE_EXPORT,
            [
                'collected_invoice_id' => $collected_invoice_id,
                'send_as_is' => false,
                'requested_by' => $created_by,
            ],
            $created_by
        );

        $second = $queue->enqueue(
            economic_transfer_queue::TYPE_COLLECTED_INVOICE_EXPORT,
            [
                'collected_invoice_id' => $collected_invoice_id,
                'send_as_is' => true,
                'requested_by' => $created_by,
            ],
            $created_by
        );

        expect((int)$first['id'])->toBeGreaterThan(0);
        expect((int)$second['id'])->toBe((int)$first['id']);

        $row = $db->fetch_assoc($db->query(
            "SELECT COUNT(*) AS cnt
             FROM economic_transfer_queue_jobs
             WHERE created_by = $created_by
               AND transfer_type = '" . economic_transfer_queue::TYPE_COLLECTED_INVOICE_EXPORT . "'"
        ));
        expect((int)($row['cnt'] ?? 0))->toBe(1);
    } finally {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);
        $db->close();
    }
});

it('processes only collected-invoice jobs and respects the manual batch limit', function (): void {
    $db = economic_transfer_queue_integration_db();
    $created_by = 921500 + random_int(1000, 9999);
    $order_id = 932000 + random_int(1000, 9999);
    $first_collected_invoice_id = 932500 + random_int(1000, 9999);
    $second_collected_invoice_id = 933000 + random_int(1000, 9999);
    $queue = new economic_transfer_queue(new EconomicTransferQueueIntegrationStubExecutor());

    try {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);

        $order_job = $queue->enqueue(
            economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT,
            [
                'order_id' => $order_id,
                'requested_by' => $created_by,
            ],
            $created_by
        );

        $first_collected_job = $queue->enqueue(
            economic_transfer_queue::TYPE_COLLECTED_INVOICE_EXPORT,
            [
                'collected_invoice_id' => $first_collected_invoice_id,
                'send_as_is' => false,
                'requested_by' => $created_by,
            ],
            $created_by
        );

        $second_collected_job = $queue->enqueue(
            economic_transfer_queue::TYPE_COLLECTED_INVOICE_EXPORT,
            [
                'collected_invoice_id' => $second_collected_invoice_id,
                'send_as_is' => true,
                'requested_by' => $created_by,
            ],
            $created_by
        );

        $summary = $queue->processPendingByTransferType(
            economic_transfer_queue::TYPE_COLLECTED_INVOICE_EXPORT,
            1
        );

        expect((int)$summary['processed'])->toBe(1);
        expect((int)$summary['completed'])->toBe(1);
        expect((int)$summary['failed'])->toBe(0);
        expect($summary['jobs'])->toHaveCount(1);
        expect($summary['jobs'][0])->toBe((int)$first_collected_job['id']);

        $processed_collected_job = $queue->getJobById((int)$first_collected_job['id']);
        $queued_collected_job = $queue->getJobById((int)$second_collected_job['id']);
        $queued_order_job = $queue->getJobById((int)$order_job['id']);

        expect($processed_collected_job)->not->toBeNull();
        expect((string)$processed_collected_job['status'])->toBe(economic_transfer_queue::STATUS_COMPLETED);
        expect((int)($processed_collected_job['result']['collected_invoice_id'] ?? 0))->toBe($first_collected_invoice_id);

        expect($queued_collected_job)->not->toBeNull();
        expect((string)$queued_collected_job['status'])->toBe(economic_transfer_queue::STATUS_QUEUED);

        expect($queued_order_job)->not->toBeNull();
        expect((string)$queued_order_job['status'])->toBe(economic_transfer_queue::STATUS_QUEUED);
    } finally {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);
        $db->close();
    }
});

it('rejects invalid payloads without inserting queue rows', function (): void {
    $db = economic_transfer_queue_integration_db();
    $created_by = 922000 + random_int(1000, 9999);
    $queue = new economic_transfer_queue(new EconomicTransferQueueIntegrationStubExecutor());

    try {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);

        $before = $db->fetch_assoc($db->query(
            "SELECT COUNT(*) AS cnt
             FROM economic_transfer_queue_jobs
             WHERE created_by = $created_by"
        ));
        $before_count = (int)($before['cnt'] ?? 0);

        expect(fn () => $queue->enqueue(
            economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT,
            [
                'order_id' => 0,
                'requested_by' => $created_by,
            ],
            $created_by
        ))->toThrow(Exception::class, 'order_id is required and must be a positive number');

        $after = $db->fetch_assoc($db->query(
            "SELECT COUNT(*) AS cnt
             FROM economic_transfer_queue_jobs
             WHERE created_by = $created_by"
        ));
        expect((int)($after['cnt'] ?? 0))->toBe($before_count);
    } finally {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);
        $db->close();
    }
});

it('supports fail retry and reprocess lifecycle transitions', function (): void {
    $db = economic_transfer_queue_integration_db();
    $created_by = 923000 + random_int(1000, 9999);
    $order_id = 933000 + random_int(1000, 9999);
    $executor = new EconomicTransferQueueIntegrationStubExecutor();
    $executor->failNextOrderDraft($order_id);
    $queue = new economic_transfer_queue($executor);

    try {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);

        $job = $queue->enqueue(
            economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT,
            [
                'order_id' => $order_id,
                'requested_by' => $created_by,
            ],
            $created_by
        );
        $job_id = (int)$job['id'];
        expect($job_id)->toBeGreaterThan(0);

        $first = $queue->processPending(1);
        expect((int)$first['processed'])->toBe(1);
        expect((int)$first['failed'])->toBe(1);

        $failed_job = $queue->getJobById($job_id);
        expect($failed_job)->not->toBeNull();
        expect((string)$failed_job['status'])->toBe(economic_transfer_queue::STATUS_FAILED);
        expect((int)$failed_job['attempts'])->toBe(1);

        $retried = $queue->retryJob($job_id);
        expect((string)$retried['status'])->toBe(economic_transfer_queue::STATUS_QUEUED);

        $second = $queue->processPending(1);
        expect((int)$second['processed'])->toBe(1);
        expect((int)$second['completed'])->toBe(1);

        $completed_job = $queue->getJobById($job_id);
        expect($completed_job)->not->toBeNull();
        expect((string)$completed_job['status'])->toBe(economic_transfer_queue::STATUS_COMPLETED);
        expect((int)($completed_job['result']['order_id'] ?? 0))->toBe($order_id);
    } finally {
        economic_transfer_queue_cleanup_for_created_by($db, $created_by);
        $db->close();
    }
});
