<?php
/**
 * Copyright © Simplyin. All rights reserved.
 */
declare(strict_types=1);

namespace Simplyin\Payment\Cron;

use Magento\Framework\App\Config\Storage\WriterInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
use Simplyin\Payment\Helper\ApiClient;
use Psr\Log\LoggerInterface;

/**
 * Daily cron to sync switchInfo (isChecked) from SimplyIN API
 *
 * - API isChecked=true  → field enabled (merchant can change the value); on the disabled→enabled
 *   transition the merchant's pre-lock value is restored from backup (once) instead of staying
 *   forced at 0.
 * - API isChecked=false → value forced to 0, field disabled (locked by SimplyIN); on the
 *   enabled→disabled transition the merchant's current value is backed up first (once) so it
 *   isn't lost while the field is locked.
 */
class SyncSwitchInfo
{
    private const CONFIG_PATH_API_KEY = 'payment/simplyin_payment/api_key';
    private const CONFIG_PATH_DEFAULT_CHECKED = 'payment/simplyin_payment/default_create_account_checked';
    private const CONFIG_PATH_DEFAULT_CHECKED_DISABLED = 'payment/simplyin_payment/default_create_account_checked_disabled';
    private const CONFIG_PATH_DEFAULT_CHECKED_BACKUP = 'payment/simplyin_payment/default_create_account_checked_backup';

    /**
     * Sentinel written to CONFIG_PATH_DEFAULT_CHECKED_BACKUP when the merchant had never
     * explicitly set a value before the field got locked (i.e. the read at lock time returned
     * nothing to preserve).
     */
    private const BACKUP_NOT_SET = '__not_set__';

    /**
     * @var WriterInterface
     */
    private WriterInterface $configWriter;

    /**
     * @var ScopeConfigInterface
     */
    private ScopeConfigInterface $scopeConfig;

    /**
     * @var ApiClient
     */
    private ApiClient $apiClient;

    /**
     * @var LoggerInterface
     */
    private LoggerInterface $logger;

    /**
     * @param WriterInterface $configWriter
     * @param ScopeConfigInterface $scopeConfig
     * @param ApiClient $apiClient
     * @param LoggerInterface $logger
     */
    public function __construct(
        WriterInterface $configWriter,
        ScopeConfigInterface $scopeConfig,
        ApiClient $apiClient,
        LoggerInterface $logger
    ) {
        $this->configWriter = $configWriter;
        $this->scopeConfig = $scopeConfig;
        $this->apiClient = $apiClient;
        $this->logger = $logger;
    }

    /**
     * Sync switchInfo (isChecked) from the SimplyIN API into store config.
     *
     * @return void
     */
    public function execute(): void
    {
        $apiKey = $this->scopeConfig->getValue(self::CONFIG_PATH_API_KEY, ScopeInterface::SCOPE_STORE);
        if (empty($apiKey)) {
            return;
        }

        try {
            $response = $this->apiClient->getPluginSyncInfo();

            if (!$response['success'] || empty($response['data'])) {
                $this->logger->warning('SimplyIN SyncSwitchInfo: Failed to fetch plugin sync info');
                return;
            }

            $data = $response['data'];

            if (!isset($data['switchInfo']['isChecked'])) {
                return;
            }

            $apiIsChecked = (bool)$data['switchInfo']['isChecked'];

            // Read the CURRENT disabled flag before this run writes anything, so we can tell a
            // real lock/unlock transition apart from a steady-state re-sync (isChecked reported
            // the same as yesterday). Backup/restore must only fire on the transition edge.
            //
            // Read at the DEFAULT scope, matching the scope every save()/delete() below writes at
            // (SCOPE_TYPE_DEFAULT, 0) — this is read-your-own-write, not a merchant-facing read, so
            // it must not pick up an unrelated store-level override and diverge from what this
            // cron itself last persisted.
            $wasDisabled = (bool)$this->scopeConfig->getValue(
                self::CONFIG_PATH_DEFAULT_CHECKED_DISABLED,
                ScopeConfigInterface::SCOPE_TYPE_DEFAULT
            );

            if ($apiIsChecked) {
                $this->handleEnabled($wasDisabled);
            } else {
                $this->handleDisabled($wasDisabled);
            }
        } catch (\Exception $e) {
            $this->logger->error('SimplyIN SyncSwitchInfo: Error', ['error' => $e->getMessage()]);
        }
    }

    /**
     * API isChecked=true → field enabled, merchant can change the value.
     *
     * On the disabled→enabled transition, restore the merchant's pre-lock value (once) before
     * clearing the lock, then clear the backup so it can't leak into a later, unrelated
     * transition. On a steady-state re-sync (was already enabled), do nothing but re-affirm the
     * disabled flag.
     *
     * @param bool $wasDisabled Whether the field was disabled before this run.
     * @return void
     */
    private function handleEnabled(bool $wasDisabled): void
    {
        if ($wasDisabled) {
            // Same read-your-own-write reasoning as in execute(): the backup was written at the
            // default scope, so it must be read back at the default scope.
            $backup = $this->scopeConfig->getValue(
                self::CONFIG_PATH_DEFAULT_CHECKED_BACKUP,
                ScopeConfigInterface::SCOPE_TYPE_DEFAULT
            );
            $restoreValue = ($backup === null || $backup === self::BACKUP_NOT_SET) ? '0' : (string)$backup;
            $this->configWriter->save(self::CONFIG_PATH_DEFAULT_CHECKED, $restoreValue, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0);
            $this->configWriter->delete(self::CONFIG_PATH_DEFAULT_CHECKED_BACKUP);
            $this->logger->info('SimplyIN SyncSwitchInfo: isChecked=true → field enabled, merchant value restored');
        } else {
            $this->logger->info('SimplyIN SyncSwitchInfo: isChecked=true → field enabled');
        }
        $this->configWriter->save(self::CONFIG_PATH_DEFAULT_CHECKED_DISABLED, '0', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0);
    }

    /**
     * API isChecked=false → force value to 0, field disabled.
     *
     * On the enabled→disabled transition, back up the merchant's current value (once) before
     * zeroing it, so it isn't lost while the field is locked. On a steady-state re-sync (was
     * already disabled), leave the existing backup alone.
     *
     * @param bool $wasDisabled Whether the field was disabled before this run.
     * @return void
     */
    private function handleDisabled(bool $wasDisabled): void
    {
        if (!$wasDisabled) {
            // Same read-your-own-write reasoning: this cron only ever forces the live value at
            // the default scope, so the value being preserved before that write must be read
            // from that same scope.
            $currentValue = $this->scopeConfig->getValue(
                self::CONFIG_PATH_DEFAULT_CHECKED,
                ScopeConfigInterface::SCOPE_TYPE_DEFAULT
            );
            $backupValue = ($currentValue === null) ? self::BACKUP_NOT_SET : (string)$currentValue;
            $this->configWriter->save(self::CONFIG_PATH_DEFAULT_CHECKED_BACKUP, $backupValue, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0);
        }
        $this->configWriter->save(self::CONFIG_PATH_DEFAULT_CHECKED, '0', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0);
        $this->configWriter->save(self::CONFIG_PATH_DEFAULT_CHECKED_DISABLED, '1', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0);
        $this->logger->info('SimplyIN SyncSwitchInfo: isChecked=false → value reset, field disabled');
    }
}
