<?php

namespace Autogedal\TbiBank\Test\Unit;

use Autogedal\TbiBank\Cron\CheckApprovals;
use Autogedal\TbiBank\Model\ApplicationRegistry;
use Autogedal\TbiBank\Model\ApprovalAccounting;
use Autogedal\TbiBank\Model\ApprovalConfig;
use Autogedal\TbiBank\Model\Environment;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\OrderFactory;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\ResourceModel\Order as OrderResource;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class ApprovalPollingTest extends TestCase
{
    private $config;
    private $registry;
    private $environment;
    private $accounting;
    private $curl;
    private $logger;
    private $order;
    private CheckApprovals $cron;

    protected function setUp(): void
    {
        $this->config = $this->createMock(ApprovalConfig::class);
        $this->config->method('pollingEnabled')->willReturn(true);
        $this->config->method('getBatchSize')->willReturn(50);
        $this->config->method('getRemoteApprovedStatuses')->willReturn(['CONFIRMED_APPROVAL']);
        $this->registry = $this->createMock(ApplicationRegistry::class);
        $this->registry->method('getCandidates')->with(50)->willReturn([
            ['order_id' => 42, 'provider_order_id' => 60, 'token' => 'encrypted-token', 'account_fingerprint' => 'same-account'],
        ]);
        $this->registry->method('token')->willReturn('decrypted-token');
        $this->environment = $this->createMock(Environment::class);
        $this->environment->method('getAccountFingerprint')->willReturn('same-account');
        $this->environment->method('getOrigin')->willReturn(Environment::UAT_ORIGIN);
        $this->environment->method('getCredentials')->willReturn(['reseller_code' => 'test-code', 'reseller_key' => 'test-key']);
        $this->accounting = $this->createMock(ApprovalAccounting::class);
        $this->curl = $this->createMock(Curl::class);
        $this->curl->method('getStatus')->willReturn(200);
        $this->logger = $this->createMock(LoggerInterface::class);
        $payment = $this->createPartialMock(Payment::class, []);
        $payment->setMethod('fusionpay');
        $this->order = $this->createPartialMock(Order::class, ['getPayment']);
        $this->order->method('getPayment')->willReturn($payment);
        $this->order->setIncrementId('AGD-123')->setState('pending')->setStatus(ApprovalConfig::WAITING_STATUS)->setStoreId(1);
        $factory = $this->createMock(OrderFactory::class);
        $factory->method('create')->willReturn($this->order);
        $resource = $this->createMock(OrderResource::class);
        $resource->method('load')->willReturnSelf();
        $this->cron = new CheckApprovals($this->config, $this->registry, $this->environment,
            $this->accounting, $factory, $resource, $this->curl, $this->logger);
    }

    public function testConfirmedApprovalUsesProviderIdAndTokenAndRunsAccounting(): void
    {
        $this->curl->method('getBody')->willReturn(json_encode(['error' => 0, 'store_order_id' => 'AGD-123', 'status' => 'CONFIRMED_APPROVAL']));
        $this->curl->expects($this->once())->method('post')->with(Environment::UAT_ORIGIN . '/api/GetApplicationStatus', $this->callback(function ($body) {
            $request = json_decode($body, true);
            $this->assertSame(60, $request['order_id']);
            $this->assertSame('decrypted-token', $request['token']);
            return true;
        }));
        $this->curl->expects($this->once())->method('setTimeout')->with(10);
        $this->accounting->expects($this->once())->method('approve')->with('AGD-123', 'Approved');
        $this->registry->expects($this->once())->method('checked')->with(42, 'CONFIRMED_APPROVAL');
        $this->cron->execute();
    }

    /** @dataProvider unapprovedStatuses */
    public function testPendingRejectedCanceledOrUnknownResponseNeverReleasesOrder(string $status): void
    {
        $this->curl->method('getBody')->willReturn(json_encode(['error' => 0, 'store_order_id' => 'AGD-123', 'status' => $status]));
        $this->accounting->expects($this->never())->method('approve');
        $this->registry->expects($this->once())->method('checked')->with(42, $status);
        $this->cron->execute();
        $this->assertSame(ApprovalConfig::WAITING_STATUS, $this->order->getStatus());
    }

    public static function unapprovedStatuses(): array
    {
        return [['PENDING'], ['REJECTED'], ['Comanda anulata TBI'], ['UNKNOWN'], ['confirmed_approval']];
    }

    public function testMerchantOrderIdentityMismatchDoesNotReleaseOrderOrLogToken(): void
    {
        $this->curl->method('getBody')->willReturn(json_encode(['error' => 0, 'store_order_id' => 'different-order', 'status' => 'CONFIRMED_APPROVAL']));
        $this->accounting->expects($this->never())->method('approve');
        $this->logger->expects($this->once())->method('warning')->with($this->callback(function ($message) {
            $this->assertStringNotContainsString('token', $message);
            return true;
        }), ['order_id' => 42]);
        $this->registry->expects($this->once())->method('checked')->with(42);
        $this->cron->execute();
    }

    public function testHttpFailureDoesNotReleaseOrder(): void
    {
        $this->curl->method('getBody')->willReturn('not JSON');
        $this->accounting->expects($this->never())->method('approve');
        $this->registry->expects($this->once())->method('checked')->with(42);
        $this->cron->execute();
    }

    public function testOrderAlreadyAdvancedIsNotPolledAgain(): void
    {
        $this->order->setStatus('custom_awb')->setState('processing');
        $this->curl->expects($this->never())->method('post');
        $this->accounting->expects($this->never())->method('approve');
        $this->registry->expects($this->once())->method('checked')->with(42);
        $this->cron->execute();
    }
}
