<?php

namespace TbiBank\FusionPay\Test\Unit\Observer;

use Magento\Config\Model\ResourceModel\Config;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\Event\Observer;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Framework\Module\Manager;
use PHPUnit\Framework\TestCase;
use TbiBank\FusionPay\Helper\Data as FusionPayHelper;
use TbiBank\FusionPay\Logger\FusionPayLogger;
use TbiBank\FusionPay\Model\Cryptor;
use TbiBank\FusionPay\Observer\AfterConfigSave;

/**
 * Behaviour under test: TbiBank\FusionPay\Observer\AfterConfigSave::execute() (CKT-3933)
 *
 * The observer writes the API answer (or the Inactive status) straight to core_config_data after
 * Magento has already re-initialised the config cache, so it has to refresh that cache itself.
 *
 * Given a save of the Fusion Pay RO settings section
 * When  the observer finishes, whatever the TBI API answered
 * Then  the config cache is cleaned after the last setting was written.
 */
class AfterConfigSaveTest extends TestCase
{
    private const STORED_SETTINGS = [
        'tbi_settings/general_settings/reseller_code' => 'S333Mag',
        'tbi_settings/general_settings/reseller_key' => 'StoreMag',
        'tbi_settings/label_settings/last_api_reseller_code' => 'S333Mag',
        'tbi_settings/label_settings/last_api_reseller_key' => 'StoreMag',
        'tbi_settings/label_settings/minimum_visible' => '150',
        'tbi_settings/label_settings/maximum_visible' => '5000',
        'tbi_settings/label_settings/tier_count' => '3',
    ];

    private const API_SETTINGS = [
        'error' => 0,
        'bnpl_period' => 4,
        'bnpl_multiplier' => 0.25,
        'bnpl_min' => 100,
        'bnpl_max' => 2000,
        'tiers' => [4000, 10000, 65000],
        'periods' => [60, 60, 60],
        'multipliers' => [0.0355, 0.0322, 0.0284],
        'tier1_min' => 1000.01,
        'bnpl_analysis_fee' => 40,
        'standard_analysis_fee' => 95,
    ];

    /** @var string[] */
    private array $calls = [];

    private function makeObserver(Curl $curl): AfterConfigSave
    {
        $config = $this->createMock(Config::class);
        $config->method('saveConfig')->willReturnCallback(function ($path) {
            $this->calls[] = 'save:' . $path;
        });

        $cacheTypeList = $this->createMock(TypeListInterface::class);
        $cacheTypeList->method('cleanType')->willReturnCallback(function ($typeCode) {
            $this->calls[] = 'clean:' . $typeCode;
        });

        $helper = $this->getMockBuilder(FusionPayHelper::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['getConfigValue'])
            ->getMock();
        $helper->method('getConfigValue')->willReturnCallback(
            fn ($path) => self::STORED_SETTINGS[$path] ?? null
        );

        $moduleManager = $this->createMock(Manager::class);
        $moduleManager->method('isEnabled')->willReturn(true);

        return new AfterConfigSave(
            $curl,
            $config,
            $cacheTypeList,
            $helper,
            $this->createMock(Cryptor::class),
            $this->createMock(FusionPayLogger::class),
            $moduleManager
        );
    }

    private function curlAnswering(int $status, array $body): Curl
    {
        $curl = $this->createMock(Curl::class);
        $curl->method('getStatus')->willReturn($status);
        $curl->method('getBody')->willReturn(json_encode($body));

        return $curl;
    }

    private function assertConfigCacheCleanedAfterLastSave(): void
    {
        $saveIndexes = array_keys(preg_grep('/^save:/', $this->calls));
        $afterLastSave = array_slice($this->calls, $saveIndexes ? max($saveIndexes) + 1 : 0);

        $this->assertContains('clean:config', $afterLastSave);
    }

    public function testSuccessfulSyncCleansConfigCacheAfterStoringApiSettings(): void
    {
        $this->makeObserver($this->curlAnswering(200, self::API_SETTINGS))->execute(new Observer());

        $this->assertContains('save:tbi_settings/label_settings/minimum_visible', $this->calls);
        $this->assertConfigCacheCleanedAfterLastSave();
    }

    public function testRejectedCredentialsStillCleanConfigCache(): void
    {
        $this->makeObserver($this->curlAnswering(200, ['error' => 401, 'message' => 'Invalid credentials.']))
            ->execute(new Observer());

        $this->assertContains('save:tbi_settings/general_settings/status', $this->calls);
        $this->assertConfigCacheCleanedAfterLastSave();
    }

    public function testHttpFailureStillCleansConfigCache(): void
    {
        $this->makeObserver($this->curlAnswering(503, []))->execute(new Observer());

        $this->assertContains('save:tbi_settings/general_settings/status', $this->calls);
        $this->assertConfigCacheCleanedAfterLastSave();
    }

    public function testTransportExceptionStillCleansConfigCache(): void
    {
        $curl = $this->createMock(Curl::class);
        $curl->method('post')->willThrowException(new \Exception('Could not resolve host'));

        $this->makeObserver($curl)->execute(new Observer());

        $this->assertConfigCacheCleanedAfterLastSave();
    }
}
