<?php

namespace Autogedal\TbiBank\Test\Unit;

use Autogedal\TbiBank\Model\ApprovalAccounting;
use Autogedal\TbiBank\Model\ApprovalConfig;
use Autogedal\TbiBank\Plugin\AwbApprovalGate;
use Autogedal\TbiBank\Plugin\OrderFlowStatus;
use Autogedal\TbiBank\Plugin\WaitingStatus;
use Eadesigndev\Awb\Model\Awb;
use Eadesigndev\Awb\Model\AwbRepository;
use Magento\Framework\Event;
use Magento\Framework\Event\Observer;
use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\OrderFactory;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\Order\Status\History;
use Magento\Sales\Model\Service\OrderService;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\ResourceModel\Order as OrderResource;
use PHPUnit\Framework\TestCase;
use TbiBank\FusionPay\Observer\SetDefaultTbiStatus;

class ApprovalGateTest extends TestCase
{
    private function order(string $method = 'fusionpay', bool $approved = false): Order
    {
        $payment = $this->createPartialMock(Payment::class, []);
        $payment->setMethod($method)->setAdditionalInformation(ApprovalAccounting::MARKER, $approved);
        $order = $this->createPartialMock(Order::class, ['getId', 'getPayment', 'addStatusHistoryComment']);
        $order->method('getId')->willReturn(42);
        $order->method('getPayment')->willReturn($payment);
        $order->method('addStatusHistoryComment')->willReturn($this->createPartialMock(History::class, []));
        $order->setState('pending')->setStatus('pending')->setStoreId(1);
        return $order;
    }

    private function gate(Order $order, bool $required = true): AwbApprovalGate
    {
        $config = $this->createMock(ApprovalConfig::class);
        $config->method('isRequired')->with(1)->willReturn($required);
        $awb = $this->createPartialMock(Awb::class, []);
        $awb->setData('order_id', 42);
        $repository = $this->createMock(AwbRepository::class);
        $repository->method('getById')->with(7)->willReturn($awb);
        $factory = $this->createMock(OrderFactory::class);
        $factory->method('create')->willReturn($order);
        $resource = $this->createMock(OrderResource::class);
        $resource->expects($this->once())->method('load')->with($order, 42)->willReturnSelf();
        return new AwbApprovalGate($config, $repository, $factory, $resource);
    }

    public function testEnabledGateBlocksCourierEvenIfOperatorMovedUnapprovedOrderToPending(): void
    {
        $this->expectException(LocalizedException::class);
        $this->expectExceptionMessage('blocked until TBI');
        $this->gate($this->order())->beforeGenerateAwb(new \stdClass(), 7);
    }

    public function testEnabledGateAllowsAccountedApproval(): void
    {
        $this->assertSame([7], $this->gate($this->order('fusionpay', true))->beforeGenerateAwb(new \stdClass(), 7));
    }

    public function testDisabledAwbGateAllowsUnapprovedTbi(): void
    {
        $this->assertSame([7], $this->gate($this->order(), false)->beforeGenerateAwb(new \stdClass(), 7));
    }

    public function testOtherPaymentMethodsAreUnaffected(): void
    {
        $this->assertSame([7], $this->gate($this->order('cashondelivery'))->beforeGenerateAwb(new \stdClass(), 7));
    }

    public function testEvenApprovedCanceledOrderIsBlocked(): void
    {
        $order = $this->order('fusionpay', true)->setState('canceled');
        $this->expectException(LocalizedException::class);
        $this->gate($order)->beforeGenerateAwb(new \stdClass(), 7);
    }

    public function testPlacementAlwaysUsesWaitingStatusForTbi(): void
    {
        $order = $this->order();
        $order->setIncrementId('100000042');
        $order->expects($this->once())->method('addStatusHistoryComment')->with(
            $this->callback(fn ($comment) => str_contains((string) $comment, 'statusul finantarii a fost initializat')),
            ApprovalConfig::WAITING_STATUS
        )->willReturn($this->createPartialMock(History::class, []));
        $observer = new Observer(['event' => new Event(['order' => $order])]);
        (new WaitingStatus())->aroundExecute(
            $this->createMock(SetDefaultTbiStatus::class),
            function () {
                $this->fail('Vendor observer must not restore Preluata/pending before approval.');
            },
            $observer
        );
        $this->assertSame('pending', $order->getState());
        $this->assertSame(ApprovalConfig::WAITING_STATUS, $order->getStatus());
        $this->assertFalse($order->getIsInProcess());
    }

    public function testOrderFlowRestoresWaitingStatusAfterGenericOrderSave(): void
    {
        $order = $this->order()->setData('entity_id', 42)->setState('pending')->setStatus('new');
        $repository = $this->createMock(OrderRepositoryInterface::class);
        $repository->expects($this->once())->method('get')->with(42)->willReturn($order);
        $repository->expects($this->once())->method('save')->with($this->callback(function ($saved) {
            $this->assertSame('pending', $saved->getState());
            $this->assertSame(ApprovalConfig::WAITING_STATUS, $saved->getStatus());
            $this->assertFalse($saved->getIsInProcess());
            return true;
        }));

        $result = (new OrderFlowStatus($repository))->aroundPlace(
            $this->createMock(OrderService::class),
            fn () => $order,
            $order
        );

        $this->assertSame($order, $result);
    }

    public function testOrderFlowDoesNotResetApprovedOrder(): void
    {
        $order = $this->order('fusionpay', true)->setData('entity_id', 42)->setState('pending')->setStatus('new');
        $repository = $this->createMock(OrderRepositoryInterface::class);
        $repository->expects($this->once())->method('get')->with(42)->willReturn($order);
        $repository->expects($this->never())->method('save');

        (new OrderFlowStatus($repository))->aroundPlace(
            $this->createMock(OrderService::class),
            fn () => $order,
            $order
        );

        $this->assertSame('pending', $order->getState());
        $this->assertSame('new', $order->getStatus());
    }
}
