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

namespace Simplyin\Payment\Cron;

use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\App\Config\Storage\WriterInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Store\Model\ScopeInterface;
use Simplyin\Payment\Helper\ApiClient;
use Simplyin\Payment\Model\Auth\RsaRegistrar;
use Psr\Log\LoggerInterface;

/**
 * Cron job to sync plugin configuration from SimplyIN API
 * Runs every 30 minutes until successfully configured, then stops
 */
class SyncPluginConfig
{
    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_TRANSACTION_HISTORY_DAYS = 'payment/simplyin_payment/transaction_history_length_days';
    private const CONFIG_PATH_FROM_DATE_ORDER_SYNC = 'payment/simplyin_payment/from_date_order_sync';
    private const CONFIG_PATH_PLUGIN_CONFIG_SYNCED = 'payment/simplyin_payment/plugin_config_synced';
    private const CONFIG_PATH_SHIPPING_METHODS = 'payment/shipping_methods/';
    private const CONFIG_PATH_WEBHOOK_SECRET = 'payment/simplyin_payment/webhook_secret';

    // GDPR and Terms URL config paths
    private const CONFIG_PATH_GDPR_URL_EN = 'payment/simplyin_payment/gdpr_url_en';
    private const CONFIG_PATH_GDPR_URL_PL = 'payment/simplyin_payment/gdpr_url_pl';
    private const CONFIG_PATH_TERMS_B2C_URL_EN = 'payment/simplyin_payment/terms_b2c_url_en';
    private const CONFIG_PATH_TERMS_B2C_URL_PL = 'payment/simplyin_payment/terms_b2c_url_pl';
    private const CONFIG_PATH_TERMS_B2B_URL_EN = 'payment/simplyin_payment/terms_b2b_url_en';
    private const CONFIG_PATH_TERMS_B2B_URL_PL = 'payment/simplyin_payment/terms_b2b_url_pl';

    /**
     * Mapping of API docsInfo keys to config paths
     */
    private const DOCS_INFO_MAPPING = [
        'gdpr-en' => self::CONFIG_PATH_GDPR_URL_EN,
        'gdpr-pl' => self::CONFIG_PATH_GDPR_URL_PL,
        'tc-b2c-en' => self::CONFIG_PATH_TERMS_B2C_URL_EN,
        'tc-b2c-pl' => self::CONFIG_PATH_TERMS_B2C_URL_PL,
        'tc-b2b-en' => self::CONFIG_PATH_TERMS_B2B_URL_EN,
        'tc-b2b-pl' => self::CONFIG_PATH_TERMS_B2B_URL_PL
    ];

    /**
     * Mapping of API logistics partners to config sort order keys
     */
    private const LOGISTICS_PARTNER_MAPPING = [
        'DHL' => 'dhl_box_pop_sort_order',
        'DPD' => 'dpd_pickup_sort_order',
        'InPost' => 'inpost_paczkomat_sort_order',
        'GLS' => 'gls_pickup_sort_order',
        'ORLEN' => 'orlen_punkty_sort_order',
        'UPS' => 'ups_pickup_sort_order',
        'FedEx' => 'fedex_pickup_sort_order'
    ];

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

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

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

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

    /**
     * @var EncryptorInterface
     */
    private $encryptor;

    /**
     * @var RsaRegistrar
     */
    private $rsaRegistrar;

    /**
     * @var TypeListInterface
     */
    private $cacheTypeList;

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

    /**
     * Execute cron job - one-time initial sync
     * Runs every 5 minutes until successfully configured, then stops.
     * switchInfo sync is handled separately by SyncSwitchInfo (daily cron).
     *
     * @return void
     */
    public function execute(): void
    {
        // RSA self-heal backstop — runs on EVERY tick, before the "already synced" short-circuit, so a
        // key cleared by revoke/rotate (412, or the 401 threshold in RsaSigner::noteRsaResponse) is
        // re-registered by the next cron without waiting for a signed checkout request. Idempotent and
        // cheap: a no-op once RSA is active for the current API key, and a no-op when no key is set.
        $this->rsaRegistrar->ensureRegistered();

        // The config sync itself is one-time — stop running it once done (RSA backstop above still runs).
        if ($this->isPluginConfigSynced()) {
            return;
        }

        // Check if API key and domain are configured
        if (!$this->isApiConfigured()) {
            $this->logger->info('SimplyIN: API key or domain not configured yet, skipping plugin config sync');
            return;
        }

        $this->logger->info('SimplyIN: Starting plugin config sync cron');

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

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

            $data = $response['data'];

            $this->syncWebhookSecret($data);
            $this->syncSwitchInfo($data);
            $this->syncTransactionHistory($data);
            $this->syncLogisticsPartnersOrder($data);
            $this->syncDocsInfo($data);

            // Mark plugin config as synced — this cron stops running after this
            $this->configWriter->save(
                self::CONFIG_PATH_PLUGIN_CONFIG_SYNCED,
                '1',
                ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                0
            );

            // Invalidate the config cache so the freshly written values (webhook_secret, synced flag,
            // history days, docs URLs...) are visible immediately. WriterInterface::save() only
            // persists to the DB; without this the running app keeps reading stale/empty config
            // (e.g. the webhook handler would not see the signing secret and HMAC would stay off).
            $this->cacheTypeList->cleanType('config');

            $this->logger->info('SimplyIN: Plugin config sync completed successfully');

        } catch (\Exception $e) {
            $this->logger->error('SimplyIN: Error syncing plugin config', [
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * Sync switchInfo on initial setup
     *
     * @param array $data
     * @return void
     */
    private function syncSwitchInfo(array $data): void
    {
        // Sync switchInfo on initial setup too
        if (isset($data['switchInfo']['isChecked'])) {
            $apiIsChecked = (bool)$data['switchInfo']['isChecked'];
            if ($apiIsChecked) {
                $this->configWriter->save(self::CONFIG_PATH_DEFAULT_CHECKED, '0', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0);
                $this->configWriter->save(self::CONFIG_PATH_DEFAULT_CHECKED_DISABLED, '0', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0);
            } else {
                $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: Initial switchInfo sync', ['isChecked' => $apiIsChecked]);
        }
    }

    /**
     * Save transactionHistoryLengthDays and calculate from_date_order_sync
     *
     * @param array $data
     * @return void
     */
    private function syncTransactionHistory(array $data): void
    {
        // Save transactionHistoryLengthDays and calculate from_date_order_sync
        if (isset($data['transactionHistoryLengthDays'])) {
            $historyDays = (int)$data['transactionHistoryLengthDays'];

            // Save transactionHistoryLengthDays
            $this->configWriter->save(
                self::CONFIG_PATH_TRANSACTION_HISTORY_DAYS,
                (string)$historyDays,
                ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                0
            );
            $this->logger->info('SimplyIN: Saved transaction_history_length_days', [
                'value' => $historyDays
            ]);

            // Calculate and save from_date_order_sync (today - transactionHistoryLengthDays)
            $fromDate = date('Y-m-d', strtotime("-{$historyDays} days"));
            $this->configWriter->save(
                self::CONFIG_PATH_FROM_DATE_ORDER_SYNC,
                $fromDate,
                ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                0
            );
            $this->logger->info('SimplyIN: Saved from_date_order_sync', [
                'value' => $fromDate,
                'history_days' => $historyDays
            ]);
        }
    }

    /**
     * Save logisticsPartnersOrder as sort orders
     *
     * @param array $data
     * @return void
     */
    private function syncLogisticsPartnersOrder(array $data): void
    {
        // Save logisticsPartnersOrder as sort orders
        if (isset($data['logisticsPartnersOrder']['order']) && is_array($data['logisticsPartnersOrder']['order'])) {
            $order = $data['logisticsPartnersOrder']['order'];
            $sortOrder = 10;

            foreach ($order as $partner) {
                if (isset(self::LOGISTICS_PARTNER_MAPPING[$partner])) {
                    $configKey = self::LOGISTICS_PARTNER_MAPPING[$partner];
                    $this->configWriter->save(
                        self::CONFIG_PATH_SHIPPING_METHODS . $configKey,
                        (string)$sortOrder,
                        ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                        0
                    );
                    $this->logger->info('SimplyIN: Saved shipping sort order', [
                        'partner' => $partner,
                        'configKey' => $configKey,
                        'sortOrder' => $sortOrder
                    ]);
                    $sortOrder += 10;
                }
            }
        }
    }

    /**
     * Save docsInfo URLs (GDPR, Terms B2C, Terms B2B for EN and PL)
     *
     * @param array $data
     * @return void
     */
    private function syncDocsInfo(array $data): void
    {
        // Save docsInfo URLs (GDPR, Terms B2C, Terms B2B for EN and PL)
        if (isset($data['docsInfo']) && is_array($data['docsInfo'])) {
            foreach ($data['docsInfo'] as $doc) {
                if (isset($doc['key']) && isset($doc['url'])) {
                    $docKey = $doc['key'];
                    if (isset(self::DOCS_INFO_MAPPING[$docKey])) {
                        $configPath = self::DOCS_INFO_MAPPING[$docKey];
                        $this->configWriter->save(
                            $configPath,
                            $doc['url'],
                            ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                            0
                        );
                        $this->logger->info('SimplyIN: Saved docs URL', [
                            'key' => $docKey,
                            'url' => $doc['url']
                        ]);
                    }
                }
            }
        }
    }

    /**
     * Persist the per-shop HMAC webhook signing secret (encrypted at rest).
     *
     * Model B: key present + non-empty => store; present + empty => explicit clear; absent => leave as-is.
     *
     * @param array $data Decoded plugin-sync payload
     * @return void
     */
    private function syncWebhookSecret(array $data): void
    {
        if (!array_key_exists('paymentWebhookSigningSecret', $data)) {
            return;
        }

        $secret = (string)$data['paymentWebhookSigningSecret'];
        if ($secret === '') {
            $this->configWriter->delete(self::CONFIG_PATH_WEBHOOK_SECRET);
        } else {
            $this->configWriter->save(
                self::CONFIG_PATH_WEBHOOK_SECRET,
                $this->encryptor->encrypt($secret),
                ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
                0
            );
        }
        $this->logger->info('SimplyIN: webhook signing secret ' . ($secret === '' ? 'cleared' : 'stored'));
    }

    /**
     * Check if API key and domain are configured
     *
     * @return bool
     */
    private function isApiConfigured(): bool
    {
        $apiKey = $this->scopeConfig->getValue(
            self::CONFIG_PATH_API_KEY,
            ScopeInterface::SCOPE_STORE
        );

        $domain = $this->scopeConfig->getValue(
            'web/secure/base_url',
            ScopeInterface::SCOPE_STORE
        );

        return !empty($apiKey) && !empty($domain);
    }

    /**
     * Check if plugin config is already synced
     *
     * @return bool
     */
    private function isPluginConfigSynced(): bool
    {
        $synced = $this->scopeConfig->getValue(
            self::CONFIG_PATH_PLUGIN_CONFIG_SYNCED,
            ScopeInterface::SCOPE_STORE
        );

        return $synced === '1';
    }
}
