<?php
declare(strict_types=1);

namespace Tests\Unit;

use App\Modules\Orders\OrderImportRepository;
use PDO;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;

final class OrderImportRepositoryTest extends TestCase
{
    private PDO $pdo;
    private OrderImportRepository $repository;

    protected function setUp(): void
    {
        $this->pdo = new PDO('sqlite::memory:');
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->pdo->sqliteCreateFunction('NOW', static fn(): string => date('Y-m-d H:i:s'));

        $this->pdo->exec(
            'CREATE TABLE orders (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                status_code TEXT,
                payment_status INTEGER,
                total_paid REAL,
                is_canceled_by_buyer INTEGER DEFAULT 0,
                source_updated_at TEXT,
                payload_json TEXT,
                fetched_at TEXT,
                updated_at TEXT
            )'
        );

        $this->repository = new OrderImportRepository($this->pdo);
    }

    public function testTotalPaidPreservedWhenPaymentStatusUnchanged(): void
    {
        $this->seedOrder([
            'status_code' => 'wyslane',
            'payment_status' => 2,
            'total_paid' => 91.00,
            'is_canceled_by_buyer' => 0,
            'payload_json' => '{"a":1}',
            'source_updated_at' => '2026-05-01 10:00:00',
            'fetched_at' => '2026-05-01 10:00:00',
        ]);

        $orderData = [
            'status_code' => 'wyslane',
            'payment_status' => 2,
            'total_paid' => 119.00,
            'is_canceled_by_buyer' => 0,
            'source_updated_at' => '2026-05-10 12:00:00',
            'payload_json' => ['a' => 2],
            'fetched_at' => '2026-05-10 12:00:00',
        ];

        $this->invokeUpdateOrderDelta(1, $orderData, true, false);

        $row = $this->fetchOrder(1);
        self::assertSame('91', $this->normalizeAmount($row['total_paid']), 'total_paid must remain at manually corrected value');
        self::assertSame(2, (int) $row['payment_status']);
        self::assertSame('2026-05-10 12:00:00', $row['source_updated_at']);
    }

    public function testTotalPaidUpdatedOnPaymentTransition(): void
    {
        $this->seedOrder([
            'status_code' => 'nieoplacone',
            'payment_status' => 0,
            'total_paid' => 0.00,
            'is_canceled_by_buyer' => 0,
            'payload_json' => '{}',
            'source_updated_at' => '2026-05-01 10:00:00',
            'fetched_at' => '2026-05-01 10:00:00',
        ]);

        $orderData = [
            'status_code' => 'w_realizacji',
            'payment_status' => 2,
            'total_paid' => 119.00,
            'is_canceled_by_buyer' => 0,
            'source_updated_at' => '2026-05-10 12:00:00',
            'payload_json' => ['a' => 1],
            'fetched_at' => '2026-05-10 12:00:00',
        ];

        $this->invokeUpdateOrderDelta(1, $orderData, false, false);

        $row = $this->fetchOrder(1);
        self::assertSame('119', $this->normalizeAmount($row['total_paid']));
        self::assertSame(2, (int) $row['payment_status']);
    }

    public function testIsCanceledByBuyerPropagatedOnSourceCancelEvenWhenPaymentStable(): void
    {
        $this->seedOrder([
            'status_code' => 'wyslane',
            'payment_status' => 2,
            'total_paid' => 91.00,
            'is_canceled_by_buyer' => 0,
            'payload_json' => '{}',
            'source_updated_at' => '2026-05-01 10:00:00',
            'fetched_at' => '2026-05-01 10:00:00',
        ]);

        $orderData = [
            'status_code' => 'anulowane',
            'payment_status' => 2,
            'total_paid' => 119.00,
            'is_canceled_by_buyer' => 1,
            'source_updated_at' => '2026-05-10 12:00:00',
            'payload_json' => ['a' => 9],
            'fetched_at' => '2026-05-10 12:00:00',
        ];

        $this->invokeUpdateOrderDelta(1, $orderData, true, true);

        $row = $this->fetchOrder(1);
        self::assertSame(1, (int) $row['is_canceled_by_buyer'], 'source cancellation must propagate');
        self::assertSame('anulowane', $row['status_code']);
        self::assertSame('91', $this->normalizeAmount($row['total_paid']), 'total_paid still protected because payment_status unchanged');
    }

    /**
     * @param array<string, mixed> $orderData
     */
    private function invokeUpdateOrderDelta(int $orderId, array $orderData, bool $paymentStatusUnchanged, bool $cancelledBySource): void
    {
        $method = new ReflectionMethod(OrderImportRepository::class, 'updateOrderDelta');
        $method->setAccessible(true);
        $method->invoke($this->repository, $orderId, $orderData, $paymentStatusUnchanged, $cancelledBySource);
    }

    /**
     * @param array<string, mixed> $values
     */
    private function seedOrder(array $values): void
    {
        $columns = array_keys($values);
        $placeholders = array_map(static fn(string $c): string => ':' . $c, $columns);
        $sql = 'INSERT INTO orders (' . implode(', ', $columns) . ', updated_at) VALUES (' . implode(', ', $placeholders) . ", '2026-05-01 10:00:00')";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($values);
    }

    /**
     * @return array<string, mixed>
     */
    private function fetchOrder(int $id): array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM orders WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        self::assertIsArray($row);
        return $row;
    }

    private function normalizeAmount(mixed $value): string
    {
        return (string) (float) $value;
    }
}
