<?php

namespace TbiBank\FusionPay\Test\Unit\Plugin;

use Magento\Checkout\Model\Session\Proxy as CheckoutSession;
use Magento\Payment\Model\Method\AbstractMethod;
use Magento\Quote\Model\Quote;
use Psr\Log\NullLogger;
use PHPUnit\Framework\TestCase;
use TbiBank\FusionPay\Helper\Data;
use TbiBank\FusionPay\Model\ProgrammaticOrderContext;
use TbiBank\FusionPay\Plugin\PaymentMethodVisibility;

/**
 * Behaviour under test: TbiBank\FusionPay\Plugin\PaymentMethodVisibility::afterIsActive()
 *
 * CKT-3844 replaces the deprecated Magento\Framework\Registry lookup
 * ($this->registry->registry('is_programmatic_order')) with a dedicated, DI-injected
 * TbiBank\FusionPay\Model\ProgrammaticOrderContext service. The externally observable rule this
 * plugin enforces is unchanged; only the mechanism for the "programmatic order" bypass changed.
 *
 * Given a Gateway-created (programmatic) order in progress
 * When  any payment method's isActive() is evaluated
 * Then  afterIsActive() forces it active — programmatic orders must never be blocked by
 *       FusionPay's own visibility rules (min/max amount, config status), because the order was
 *       already placed server-side by the time this check would run.
 */
class PaymentMethodVisibilityTest extends TestCase
{
    private function makeSubject(string $code = 'fusionpay'): AbstractMethod
    {
        return new class($code) extends AbstractMethod {
            public function __construct(string $code)
            {
                $this->setCode($code);
            }
        };
    }

    private function makeHelper(array $configValues): Data
    {
        $helper = $this->getMockBuilder(Data::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['getConfigValue'])
            ->getMock();
        $helper->method('getConfigValue')->willReturnCallback(
            fn ($path) => $configValues[$path] ?? null
        );
        return $helper;
    }

    private function makeCheckoutSession(float $grandTotal): CheckoutSession
    {
        $session = new CheckoutSession();
        $session->setQuote((new Quote())->setGrandTotal($grandTotal));
        return $session;
    }

    /**
     * Given the current request was created programmatically via Gateway::execute()
     * When  afterIsActive() runs for ANY payment method, with the underlying $result false
     * Then  it returns true unconditionally, bypassing every other rule (config status, amount
     *       thresholds) — this is the core regression this diff protects: the Registry->
     *       ProgrammaticOrderContext swap must preserve this bypass exactly.
     */
    public function testProgrammaticOrderContextBypassesVisibilityRegardlessOfResult(): void
    {
        $context = new ProgrammaticOrderContext();
        $context->activate();

        $helper = $this->makeHelper(['tbi_settings/general_settings/status' => 'Inactive']); // would normally hide it
        $plugin = new PaymentMethodVisibility($this->makeCheckoutSession(0.0), $context, $helper, new NullLogger());

        $result = $plugin->afterIsActive($this->makeSubject('fusionpay'), false);

        $this->assertTrue($result);
    }

    /**
     * Given the context was never activated for this request
     * When  afterIsActive() runs for a non-fusionpay method
     * Then  the original $result passes through untouched — this plugin must not affect
     *       unrelated payment methods.
     */
    public function testNonFusionPayMethodPassesThroughOriginalResultWhenNotProgrammatic(): void
    {
        $context = new ProgrammaticOrderContext();
        $helper = $this->makeHelper([]);
        $plugin = new PaymentMethodVisibility($this->makeCheckoutSession(100.0), $context, $helper, new NullLogger());

        $this->assertTrue($plugin->afterIsActive($this->makeSubject('checkmo'), true));
        $this->assertFalse($plugin->afterIsActive($this->makeSubject('checkmo'), false));
    }

    /**
     * Given the context was never activated (a normal storefront checkout)
     * When  FusionPay's own availability rule would hide the method (module configured inactive)
     * Then  afterIsActive() returns false — proving the bypass is genuinely conditional on
     *       ProgrammaticOrderContext::isActive(), not a hard-coded pass-through introduced by
     *       the refactor.
     */
    public function testFusionPayHiddenWhenModuleInactiveAndNotProgrammatic(): void
    {
        $context = new ProgrammaticOrderContext(); // not activated
        $helper = $this->makeHelper(['tbi_settings/general_settings/status' => 'Inactive']);
        $plugin = new PaymentMethodVisibility($this->makeCheckoutSession(500.0), $context, $helper, new NullLogger());

        $result = $plugin->afterIsActive($this->makeSubject('fusionpay'), true);

        $this->assertFalse($result);
    }

    /**
     * Given the context was never activated, the module is active, and the cart total falls
     * strictly between the configured min/max visible amounts (max raised via the highest
     * configured tier, per isFusionPayAvailable()'s existing tier-scanning logic)
     * When  afterIsActive() runs for the fusionpay method with the underlying $result already
     *       true (afterIsActive only ever forces a method OFF when unavailable; when available
     *       it defers to whatever $result Magento's own isActive() already computed)
     * Then  the original (true) $result passes through — the ordinary storefront visibility
     *       rule still works exactly as before this refactor.
     */
    public function testFusionPayVisibleWithinConfiguredAmountRangeAndNotProgrammatic(): void
    {
        $context = new ProgrammaticOrderContext();
        $helper = $this->makeHelper([
            'tbi_settings/general_settings/status' => 'Active',
            'tbi_settings/label_settings/bnpl_min' => 100,
            'tbi_settings/label_settings/tier_count' => 1,
            'tbi_settings/label_settings/tier1' => 5000,
            'tbi_settings/label_settings/minimum_visible' => 100,
            'tbi_settings/label_settings/maximum_visible' => 5000,
        ]);
        $plugin = new PaymentMethodVisibility($this->makeCheckoutSession(1000.0), $context, $helper, new NullLogger());

        $this->assertTrue($plugin->afterIsActive($this->makeSubject('fusionpay'), true));
    }
}
