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

namespace Simplyin\Payment\Service\Api;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;
use Simplyin\Payment\Helper\Encryption;
use Simplyin\Payment\Http\SigningCurlClient;
use Simplyin\Payment\Model\Config\EnvironmentConfig;

/**
 * Shared HTTP transport and configuration plumbing for the SimplyIN API.
 *
 * Holds the curl client, config readers and request builder used by the
 * domain sub-clients. Behaviour is byte-identical to the original ApiClient.
 */
class ApiTransport
{
    public const XML_PATH_API_BASE_URL = 'payment/simplyin_payment/api_base_url';
    public const XML_PATH_API_KEY = 'payment/simplyin_payment/api_key';
    public const XML_PATH_STORE_NAME = 'general/store_information/name';

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

    /**
     * @var SigningCurlClient
     */
    protected SigningCurlClient $curl;

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

    /**
     * @var Encryption
     */
    protected Encryption $simplyinEncryption;

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

    /**
     * @var StoreManagerInterface
     */
    protected StoreManagerInterface $storeManager;

    /**
     * @var UrlInterface
     */
    protected UrlInterface $urlBuilder;

    /**
     * @var EnvironmentConfig
     */
    protected EnvironmentConfig $environmentConfig;

    /**
     * Constructor
     *
     * @param ScopeConfigInterface $scopeConfig
     * @param SigningCurlClient $curl
     * @param EncryptorInterface $encryptor
     * @param Encryption $simplyinEncryption
     * @param LoggerInterface $logger
     * @param StoreManagerInterface $storeManager
     * @param UrlInterface $urlBuilder
     * @param EnvironmentConfig $environmentConfig
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig,
        SigningCurlClient $curl,
        EncryptorInterface $encryptor,
        Encryption $simplyinEncryption,
        LoggerInterface $logger,
        StoreManagerInterface $storeManager,
        UrlInterface $urlBuilder,
        EnvironmentConfig $environmentConfig
    ) {
        $this->scopeConfig = $scopeConfig;
        $this->curl = $curl;
        $this->encryptor = $encryptor;
        $this->simplyinEncryption = $simplyinEncryption;
        $this->logger = $logger;
        $this->storeManager = $storeManager;
        $this->urlBuilder = $urlBuilder;
        $this->environmentConfig = $environmentConfig;
    }

    /**
     * Get curl client (RSA-signing wrapper over Magento's Curl)
     *
     * @return SigningCurlClient
     */
    public function getCurl(): SigningCurlClient
    {
        return $this->curl;
    }

    /**
     * Get scope config
     *
     * @return ScopeConfigInterface
     */
    public function getScopeConfig(): ScopeConfigInterface
    {
        return $this->scopeConfig;
    }

    /**
     * Get SimplyIN encryption helper
     *
     * @return Encryption
     */
    public function getSimplyinEncryption(): Encryption
    {
        return $this->simplyinEncryption;
    }

    /**
     * Get logger
     *
     * @return LoggerInterface
     */
    public function getLogger(): LoggerInterface
    {
        return $this->logger;
    }

    /**
     * Get URL builder
     *
     * @return UrlInterface
     */
    public function getUrlBuilder(): UrlInterface
    {
        return $this->urlBuilder;
    }

    /**
     * Get API base URL
     *
     * Resolved from the build-time environment binding (Environment::APP_ENV) — NOT from
     * admin/core_config_data — so a merchant cannot repoint the shop at the wrong (or another
     * tenant's) backend from the admin panel.
     *
     * @return string
     */
    public function getApiBaseUrl(): string
    {
        return $this->environmentConfig->getApiBaseUrl();
    }

    /**
     * Get API key
     *
     * Always returns a string ('' when unset) so every caller sends a well-formed header and
     * no null ever reaches Curl.
     *
     * @return string
     */
    public function getApiKey(): string
    {
        $encryptedKey = $this->scopeConfig->getValue(
            self::XML_PATH_API_KEY,
            ScopeInterface::SCOPE_STORE
        );

        if (!empty($encryptedKey)) {
            return (string)$this->encryptor->decrypt($encryptedKey);
        }

        return '';
    }

    /**
     * Get shop name
     *
     * Falls back to Magento store name if Simply.IN shop name is not configured
     *
     * @return string
     */
    public function getShopName(): string
    {
        $shopName = $this->scopeConfig->getValue(
            self::XML_PATH_STORE_NAME,
            ScopeInterface::SCOPE_STORE
        );

        return $shopName ?: 'Magento Store';
    }

    /**
     * Get origin domain from web/secure/base_url config or store manager
     *
     * Returns clean URL without trailing slash and without index.php
     *
     * @return string
     */
    public function getOrigin(): string
    {
        $baseUrl = (string)$this->scopeConfig->getValue(
            'web/secure/base_url',
            ScopeInterface::SCOPE_STORE
        );

        if (empty($baseUrl)) {
            try {
                $baseUrl = $this->storeManager->getStore()->getBaseUrl();
            } catch (\Exception $e) {
                return '';
            }
        }

        $baseUrl = (string)preg_replace('#/index\.php/?#', '/', $baseUrl);
        $baseUrl = rtrim($baseUrl, '/');

        return $baseUrl;
    }

    /**
     * Make API request
     *
     * @param string $endpoint
     * @param string $method
     * @param array|null $data
     * @param string|null $authToken
     * @return array
     */
    public function makeRequest($endpoint, $method = 'GET', $data = null, $authToken = null)
    {
        $url = $this->getApiBaseUrl() . '/' . ltrim($endpoint, '/');

        if ($authToken) {
            $url .= (strpos($url, '?') === false ? '?' : '&') . 'api_token=' . urlencode($authToken);
        }
        $apiKey = $this->getApiKey();

        // getApiKey() always returns a string; an empty value is still recognized by
        // SigningCurlClient as an authenticated request (it strips the key and RSA-signs, or fails closed).
        $this->curl->setHeaders([
            'Content-Type' => 'application/json',
            'Origin' => $this->getOrigin(),
            'X-Auth-Merchant-Api-Key' => $apiKey
        ]);

        try {
            // makeRequest() only handles GET and POST. Callers that need a different verb
            // (e.g. AccountApiClient::updateUserData() issues PATCH via CURLOPT_CUSTOMREQUEST)
            // set it on the curl client directly. isPostRequest() warns on any other verb
            // passed here and falls back to GET (non-blocking).
            $isPost = $this->isPostRequest($method, $endpoint);

            if ($data !== null && $isPost) {
                $this->curl->post($url, json_encode($data));
            } else {
                $this->curl->get($url);
            }

            $response = $this->curl->getBody();
            $statusCode = $this->curl->getStatus();

            return $this->buildMakeRequestResponse($response, $statusCode);
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Decide whether the request is a POST; warn on unsupported HTTP verbs.
     *
     * Only GET and POST are handled by makeRequest(). Anything else passed here (a typo,
     * or a verb such as PUT/PATCH that other clients issue directly via CURLOPT_CUSTOMREQUEST)
     * is logged as a warning and falls back to GET (non-blocking).
     *
     * @param string $method
     * @param string $endpoint
     * @return bool
     */
    private function isPostRequest($method, $endpoint): bool
    {
        $normalizedMethod = strtoupper($method);
        if ($normalizedMethod !== 'GET' && $normalizedMethod !== 'POST') {
            $this->logger->warning(
                'SimplyIN ApiTransport: unsupported HTTP method, defaulting to GET',
                ['method' => $method, 'endpoint' => $endpoint]
            );
        }

        return $normalizedMethod === 'POST';
    }

    /**
     * Build response array for makeRequest based on status code
     *
     * @param string $response
     * @param int $statusCode
     * @return array
     */
    private function buildMakeRequestResponse($response, $statusCode)
    {
        if ($statusCode >= 200 && $statusCode < 300) {
            $decoded = json_decode($response, true);
            return [
                'success' => true,
                'data' => $decoded
            ];
        } else {
            $decoded = json_decode($response, true);
            $errorMsg = isset($decoded['message']) ? $decoded['message'] :
                (isset($decoded['error']) ? $decoded['error'] : 'API request failed');

            return [
                'success' => false,
                'error' => $errorMsg,
                'statusCode' => $statusCode,
                'response' => $response
            ];
        }
    }
}
