<?php

/**
 * Copyright © Resurs Bank AB. All rights reserved.
 * See LICENSE for license details.
 */

declare(strict_types=1);

namespace Resursbank\MerchantApi\Controller\Admin\Config;

use Context;
use Exception;
use Module;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use Resursbank;
use Resursbank\Ecom\Lib\Api\Environment;
use Resursbank\Ecom\Module\PaymentMethod\Repository;
use Resursbank\Ecom\Module\Widget\CallbackList\Html as Callback;
use Resursbank\Ecom\Module\Widget\GetPeriods\Js as GetPeriods;
use Resursbank\Ecom\Module\Widget\GetPeriods\Js as GetPeriodsJs;
use Resursbank\Ecom\Module\Widget\GetStores\Js as GetStores;
use Resursbank\Ecom\Module\Widget\PaymentMethod\Html as PaymentMethods;
use Resursbank\Ecom\Module\Widget\SupportInfo\Css as SupportInfoCss;
use Resursbank\Ecom\Module\Widget\SupportInfo\Html as SupportInfo;
use Resursbank\MerchantApi\Config\Config;
use Resursbank\MerchantApi\Config\Form\Builder;
use Resursbank\MerchantApi\Exception\FakeContainerException;
use Resursbank\MerchantApi\Logger\LoggerInterface;
use Resursbank\MerchantApi\Service\FakeContainer;
use Resursbank\MerchantApi\Util\Log;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

/**
 * Sync data from Resurs Bank API to local database.
 */
class View extends FrameworkBundleAdminController
{
    /**
     * @var resursbank
     */
    private Resursbank $module;

    /**
     * @param LoggerInterface $logger
     * @param Builder $builder
     * @param Config $config
     */
    public function __construct(
        private readonly LoggerInterface $logger,
        private readonly Builder $builder,
        private readonly Config $config
    ) {
        parent::__construct();

        $this->module = Module::getInstanceByName('resursbank');
    }

    /**
     * Fetch payment methods from API and sync them to database.
     *
     * @return Response
     *
     * @throws Exception
     *
     * @todo Our view will statically render a table containing our payment
     * @todo methods, we could use HelperList instead, but it may be preferable
     * @todo to render this ourselves so we can centralise it to ECom.
     * @todo It's a little unclear right now what the list of payment methods
     * @todo display. The list won't update as you flip between prod / test.
     * @todo Since both credentials are always displayed this should be addressed.
     * @noinspection PhpArgumentWithoutNamedIdentifierInspection
     */
    public function execute(): Response
    {
        try {
            $env = $this->config->getEnvironment();

            $hasSecretTest = $this->config->hasClientSecret(Environment::TEST);
            $hasSecretProd = $this->config->hasClientSecret(Environment::PROD);

            $supportInfoCss = (new SupportInfoCss())->content;
            $supportInfoWidget = new SupportInfo(
                minimumPhpVersion: Resursbank::PHP_VERSION_MIN,
                maximumPhpVersion: Resursbank::PHP_VERSION_MAX,
                pluginVersion: Module::getInstanceByName(
                    module_name: 'resursbank'
                )->version
            );

            $getPeriodsJs = (new GetPeriodsJs(
                methodElementId: 'form_partpayment_payment_method',
                periodElementId: 'form_partpayment_annuity_period',
                selectedPaymentMethod: $this->config->getPartPaymentPaymentMethod(),
                selectedPeriod: $this->config->getPartPaymentAnnuityPeriod(),
            ))->content;

            return $this->render('@Modules/resursbank/views/templates/admin/config.twig', [
                'layoutTitle' => $this->module->l('Resurs Bank'),
                'requireAddonsSearch' => true,
                'enableSidebar' => true,
                'help_link' => '',
                'form' => $this->builder->getForm()->createView(),
                // @todo We should pass a shopConstraint here for multistore support.
                'validateTestCredentials' => false,
                'validateProdCredentials' => false,
                'timeoutTestException' => false,
                'timeoutProdException' => false,
                'hasTestCredentials' => $this->hasCredentials(Environment::TEST),
                'hasProdCredentials' => $this->hasCredentials(Environment::PROD),
                'hasSecretTest' => $hasSecretTest,
                'hasSecretProd' => $hasSecretProd,
                'paymentMethodsWidget' => $this->getPaymentMethodsWidget(),
                'callbackWidget' => $this->getCallbackWidget(),
                'callbackTestTriggered' => $this->getCallbackTestTriggered(),
                'callbackTestReceived' => $this->getCallbackTestReceived(),
                'getPeriodsWidget' => $this->hasCredentials(env: $env) ? $getPeriodsJs : '',
                'getStoresWidget' => $this->hasCredentials(env: $env) ? $this->getStoresWidget()?->content : '',
                'supportInfoWidget' => $supportInfoWidget,
                'supportInfoCss' => $supportInfoCss,
            ]);
        } catch (Exception $e) {
            $this->logger->exception($e);
            throw $e;
        }
    }

    /**
     * @param Environment $env
     *
     * @return bool
     *
     * @since 1.0.0
     */
    private function hasCredentials(Environment $env): bool
    {
        return $this->config->hasCredentials($env);
    }

    public function getStoresWidget(): ?GetStores
    {
        try {
            return new GetStores(
                storeSelectId: 'form_api_store_id',
                environmentSelectId: 'form_api_environment',
                fetchBtnId: 'form_api_fetch_stores',
            );
        } catch (Throwable $error) {
            Log::error(message: $error);
        }

        return null;
    }

    public function getPeriodsWidget(): ?GetPeriods
    {
        try {
            if ($this->config->getStoreId() !== '') {
                return new GetPeriods(
                    methodElementId: 'form_partpayment_payment_method',
                    periodElementId: 'form_partpayment_annuity_period',
                    selectedPaymentMethod: $this->config->getPartPaymentPaymentMethod(),
                    selectedPeriod: $this->config->getPartPaymentAnnuityPeriod(),
                );
            }
        } catch (Throwable $error) {
            Log::error(message: $error);
        }

        return null;
    }

    /**
     * Attempt to fetch a callback widget.
     *
     * @return Callback|null
     */
    public function getCallbackWidget(): ?Callback
    {
        try {
            $config = FakeContainer::getConfig();
        } catch (FakeContainerException $e) {
            Log::error(message: $e);
            return null;
        }

        $xdebugSession = $config?->getXdebugSession() ?? '';

        $url = Context::getContext()->link->getModuleLink(
            module: 'resursbank',
            controller: 'authorization'
        );

        if (!empty($xdebugSession)) {
            $separator = str_contains($url, '?') ? '&' : '?';
            $url .= $separator . 'XDEBUG_SESSION_START=' . urlencode($xdebugSession);
        }

        try {
            return new Callback(
                authorizationUrl: $url
            );
        } catch (Throwable $error) {
            Log::error(message: $error);
        }

        return null;
    }


    /**
     * Get callback test triggered value.
     *
     * @return string|null
     */
    public function getCallbackTestTriggered(): ?string
    {
        return $this->config->getCallbackTestTriggered();
    }

    /**
     * Get callback test received value.
     *
     * @return string|null
     */
    public function getCallbackTestReceived(): ?string
    {
        return $this->config->getCallbackTestReceived();
    }

    /**
     * Get payment methods widget.
     *
     * @return PaymentMethods|null
     */
    public function getPaymentMethodsWidget(): ?PaymentMethods
    {
        try {
            // Without credentials, we are guaranteed an error when attempting
            // to fetch payment methods.
            if (!$this->config->hasCredentials($this->config->getEnvironment())) {
                return null;
            }

            return new PaymentMethods(
                paymentMethods: Repository::getPaymentMethods()
            );
        } catch (Throwable $error) {
            Log::error(message: $error);
        }

        return null;
    }
}
