<?php

namespace Autogedal\TbiBank\Test\Unit;

use Autogedal\TbiBank\Model\ApprovalAccounting;
use Autogedal\TbiBank\Model\ApprovalConfig;
use Autogedal\TbiBank\Model\CallbackStatusUpdate;
use Autogedal\TbiBank\Plugin\ApprovalStatus;
use Autogedal\TbiBank\Plugin\AwbNoCod;
use Eadesigndev\Awb\Model\Awb;
use Eadesigndev\Awb\Model\ResourceModel\Awb as AwbResource;
use Magento\Framework\Phrase;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Payment;
use PHPUnit\Framework\TestCase;
use TbiBank\FusionPay\Model\TbiStatusUpdate;

class ApprovalAndAwbPluginsTest extends TestCase
{
    private function plugin(
        ApprovalAccounting $accounting,
        string $status = 'Approved',
        ?CallbackStatusUpdate $callbackStatusUpdate = null
    ): ApprovalStatus
    {
        $config = $this->createMock(ApprovalConfig::class);
        $config->method('getCallbackApprovedStatus')->willReturn($status);
        return new ApprovalStatus($accounting, $config, $callbackStatusUpdate ?: $this->createMock(CallbackStatusUpdate::class));
    }

    public function testConfiguredSourceStatusTriggersAccounting(): void
    {
        $accounting = $this->createMock(ApprovalAccounting::class);
        $accounting->expects($this->once())->method('approve')->with('123', 'Confirmed', 'CONTRACT-42');
        $this->plugin($accounting, 'Confirmed')->aroundUpdateStatus(
            $this->createMock(TbiStatusUpdate::class),
            function () { $this->fail('Configured approval must be accounted.'); },
            '123', new Phrase('Confirmed'), 'CONTRACT-42'
        );
    }

    /** @dataProvider nonmatchingConfiguredStatuses */
    public function testUnconfiguredOrNonmatchingStatusNeverRecordsPayment(string $configured, string $received): void
    {
        $accounting = $this->createMock(ApprovalAccounting::class);
        $accounting->expects($this->never())->method('approve');
        $callback = $this->createMock(CallbackStatusUpdate::class);
        $callback->expects($this->once())->method('update')->with('123', $received, 'CONTRACT-42');
        $this->plugin($accounting, $configured, $callback)->aroundUpdateStatus(
            $this->createMock(TbiStatusUpdate::class),
            function () { $this->fail('Non-approval must not call the vendor updater.'); },
            '123', $received, 'CONTRACT-42'
        );
    }

    public static function nonmatchingConfiguredStatuses(): array
    {
        return [['Confirmed', 'Approved'], ['Approved', 'approved'], ['', ''], ['', 'Approved']];
    }

    public function testApprovedPhraseUsesSourceTextAndPreservesOrderNumber(): void
    {
        $accounting = $this->createMock(ApprovalAccounting::class);
        $phrase = new Phrase('Approved');
        $accounting->expects($this->once())->method('approve')->with('AGD-123', (string) $phrase);
        $proceed = function () {
            $this->fail('Approval must not call the vendor integer-casting updater.');
        };
        ($this->plugin($accounting))->aroundUpdateStatus($this->createMock(TbiStatusUpdate::class), $proceed, 'AGD-123', $phrase);
    }

    public function testContractIdIsForwardedForNonapproval(): void
    {
        $accounting = $this->createMock(ApprovalAccounting::class);
        $accounting->expects($this->never())->method('approve');
        $callback = $this->createMock(CallbackStatusUpdate::class);
        $callback->expects($this->once())->method('update')->with('123', 'Rejected', 'CONTRACT-42');
        ($this->plugin($accounting, 'Approved', $callback))->aroundUpdateStatus(
            $this->createMock(TbiStatusUpdate::class),
            function () { $this->fail('Non-approval must not call the vendor updater.'); },
            '123', 'Rejected', 'CONTRACT-42'
        );
    }

    public function testApprovedContractIdIsPassedToAccounting(): void
    {
        $accounting = $this->createMock(ApprovalAccounting::class);
        $accounting->expects($this->once())->method('approve')->with('123', 'Approved', 'CONTRACT-42');
        ($this->plugin($accounting))->aroundUpdateStatus(
            $this->createMock(TbiStatusUpdate::class),
            function () { $this->fail('Vendor updater must not handle approvals.'); },
            '123', 'Approved', 'CONTRACT-42'
        );
    }

    /** @dataProvider otherStatuses */
    public function testNonapprovalStatusDoesNotRecordPayment(string $status): void
    {
        $accounting = $this->createMock(ApprovalAccounting::class);
        $accounting->expects($this->never())->method('approve');
        $phrase = new Phrase($status);
        $callback = $this->createMock(CallbackStatusUpdate::class);
        $callback->expects($this->once())->method('update')->with('123', $status, null);
        $result = ($this->plugin($accounting, 'Approved', $callback))->aroundUpdateStatus(
            $this->createMock(TbiStatusUpdate::class),
            function () { $this->fail('Non-approval must not call the vendor updater.'); }, '123', $phrase
        );
        $this->assertNull($result);
    }

    public static function otherStatuses(): array
    {
        return [['In Progress'], ['Rejected'], ['Canceled'], ['Draft']];
    }

    /** @dataProvider awbCases */
    public function testAwbSaveAndReloadNeverCollectTbiCod(string $method, string $tbiStatus, float $expected): void
    {
        $payment = $this->createPartialMock(Payment::class, []);
        $payment->setMethod($method);
        $order = $this->createPartialMock(Order::class, ['getPayment']);
        $order->method('getPayment')->willReturn($payment);
        $order->setData('tbi_status', $tbiStatus);
        $repository = $this->createMock(OrderRepositoryInterface::class);
        $repository->method('get')->with(42)->willReturn($order);
        $resource = $this->createMock(AwbResource::class);
        $awb = $this->createPartialMock(Awb::class, []);
        $awb->setData(['order_id' => 42, 'repayment_value' => 100.0]);
        $plugin = new AwbNoCod($repository);
        $this->assertSame([$awb], $plugin->beforeSave($resource, $awb));
        $this->assertSame($expected, $awb->getData('repayment_value'));
        $awb->setData('repayment_value', 100.0);
        $this->assertSame($resource, $plugin->afterLoad($resource, $resource, $awb, 17));
        $this->assertSame($expected, $awb->getData('repayment_value'));
    }

    public static function awbCases(): array
    {
        return [['fusionpay', 'Approved', 0.0], ['fusionpay', 'In Progress', 0.0],
            ['fusionpay', 'Rejected', 0.0], ['fusionpay', 'Canceled', 0.0], ['cashondelivery', '', 100.0],
            ['paypo', '', 100.0]];
    }

    public function testAwbWithoutLinkedOrderIsNotChanged(): void
    {
        $repository = $this->createMock(OrderRepositoryInterface::class);
        $repository->expects($this->never())->method('get');
        $awb = $this->createPartialMock(Awb::class, []);
        $awb->setData('repayment_value', 25.0);
        (new AwbNoCod($repository))->beforeSave($this->createMock(AwbResource::class), $awb);
        $this->assertSame(25.0, $awb->getData('repayment_value'));
    }
}
