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

namespace Simplyin\Payment\Service\Api;

/**
 * Payment method, saved card and payment status SimplyIN API operations.
 */
class PaymentApiClient
{
    /**
     * @var ApiTransport
     */
    private ApiTransport $transport;

    /**
     * @var AccountApiClient
     */
    private AccountApiClient $accountApiClient;

    /**
     * Constructor
     *
     * @param ApiTransport $transport
     * @param AccountApiClient $accountApiClient
     */
    public function __construct(ApiTransport $transport, AccountApiClient $accountApiClient)
    {
        $this->transport = $transport;
        $this->accountApiClient = $accountApiClient;
    }

    /**
     * Get payment methods
     *
     * @return array
     */
    public function getPaymentMethods()
    {
        $response = $this->transport->makeRequest('payments/getAvailablePaymentMethods', 'GET');
        return $this->rejectUnauthenticatedMethods($response);
    }

    /**
     * Get available payment methods
     *
     * The endpoint takes no amount/currency query, so this method has no parameters;
     * callers that previously passed amount/currency were ignored.
     *
     * @return array
     */
    public function getAvailablePaymentMethods()
    {
        $response = $this->transport->makeRequest('payments/getAvailablePaymentMethods', 'GET');
        return $this->rejectUnauthenticatedMethods($response);
    }

    /**
     * Fail closed on an unauthenticated available-payment-methods response.
     *
     * The getAvailablePaymentMethods endpoint is merchant-scoped ONLY when the request is
     * RSA-authenticated. Clean RSA-only sends no per-request API key, so when RSA is not active the call goes out
     * unauthenticated and the backend answers 200 with a generic, UN-scoped full method set. Trusting
     * it would render every provider method (not just the merchant's Card/BLIK) and show the paywall
     * even for shops with nothing configured. Reject such a response; the next call self-registers and
     * signs. MUST be called immediately after the makeRequest() it guards, before any other transport
     * call overwrites the per-request signed flag.
     *
     * @param array $response
     * @return array
     */
    private function rejectUnauthenticatedMethods(array $response): array
    {
        // Only a *successful* response is dangerous: an unauthenticated request still gets a 200 with
        // a generic, un-scoped method set. If makeRequest() already failed, keep its specific error
        // rather than masking it with the generic authentication message.
        if (($response['success'] ?? false) && !$this->transport->getCurl()->wasLastRequestSigned()) {
            return [
                'success' => false,
                'error' => __('Payment methods unavailable: request could not be authenticated')
            ];
        }

        return $response;
    }

    /**
     * Get saved cards from user data
     *
     * @param string $authToken
     * @return array
     */
    public function getSavedCards($authToken)
    {
        if (empty($authToken)) {
            return [
                'success' => false,
                'error' => __('Auth token is required'),
                'savedCards' => []
            ];
        }

        $userData = $this->accountApiClient->getUserData($authToken);

        if (!$userData['success']) {
            return [
                'success' => false,
                'error' => $userData['error'] ?? __('Failed to get user data'),
                'savedCards' => []
            ];
        }

        $savedCards = $this->extractSavedCards($userData);

        return [
            'success' => true,
            'savedCards' => $savedCards,
            'userData' => $userData['data']
        ];
    }

    /**
     * Extract saved cards from user data, normalizing across possible key locations
     *
     * @param array $userData
     * @return array
     */
    private function extractSavedCards($userData)
    {
        $savedCards = [];
        if (isset($userData['data']['savedCards']) && is_array($userData['data']['savedCards'])) {
            $savedCards = $userData['data']['savedCards'];
        } elseif (isset($userData['data']['cards']) && is_array($userData['data']['cards'])) {
            $savedCards = $userData['data']['cards'];
        } elseif (isset($userData['data']['paymentMethods']) && is_array($userData['data']['paymentMethods'])) {
            $savedCards = $this->filterCardMethods($userData['data']['paymentMethods']);
        }

        return $savedCards;
    }

    /**
     * Filter payment methods down to card-type entries.
     *
     * @param array $methods
     * @return array
     */
    private function filterCardMethods(array $methods): array
    {
        $cards = [];
        foreach ($methods as $method) {
            if (isset($method['type']) && in_array($method['type'], ['card', 'saved_card', 'cc'])) {
                $cards[] = $method;
            }
        }

        return $cards;
    }

    /**
     * Get payment methods with full details including iframeUrl, integratorId, and saved cards
     *
     * This method combines payment method data with CCIframe configuration for 3D Secure payments
     *
     * @param string $authToken User authentication token
     * @return array Response containing:
     *               - success: boolean indicating if the request was successful
     *               - paymentMethods: array of available payment methods
     *               - iframeUrl: URL to load CCIframe library
     *               - integratorId: ID for CCIframe initialization
     *               - savedCards: array of user's saved cards
     *               - error: error message if request failed
     */
    public function getPaymentMethodsWithDetails($authToken)
    {
        // Fetch available payment methods from API
        $paymentMethodsResponse = $this->transport->makeRequest('payments/getAvailablePaymentMethods', 'GET', null, $authToken);
        // Fail closed before any other transport call runs (getSavedCards below would overwrite the flag).
        $paymentMethodsResponse = $this->rejectUnauthenticatedMethods($paymentMethodsResponse);

        if (!$paymentMethodsResponse['success']) {
            return [
                'success' => false,
                'error' => $paymentMethodsResponse['error'] ?? __('Failed to fetch payment methods'),
                'paymentMethods' => [],
                'iframeUrl' => '',
                'integratorId' => '',
                'savedCards' => []
            ];
        }

        $data = $paymentMethodsResponse['data'] ?? [];

        // Extract payment methods array
        $paymentMethods = $this->extractPaymentMethods($data);

        // Extract iframeUrl for CCIframe library
        $iframeUrl = $this->extractIframeUrl($data);

        // Extract integratorId for CCIframe initialization
        $integratorId = $this->extractIntegratorId($data);

        // Resolve saved cards from auth token and/or payment methods response
        $savedCards = $this->resolveSavedCards($authToken, $data);

        return [
            'success' => true,
            'paymentMethods' => $paymentMethods,
            'iframeUrl' => $iframeUrl,
            'integratorId' => $integratorId,
            'savedCards' => $savedCards
        ];
    }

    /**
     * Extract payment methods array, normalizing across possible key locations
     *
     * @param array $data
     * @return array
     */
    private function extractPaymentMethods($data)
    {
        $paymentMethods = [];
        if (isset($data['paymentMethods']) && is_array($data['paymentMethods'])) {
            $paymentMethods = $data['paymentMethods'];
        } elseif (isset($data['data']['paymentMethods']) && is_array($data['data']['paymentMethods'])) {
            $paymentMethods = $data['data']['paymentMethods'];
        } elseif (is_array($data) && !isset($data['iframeUrl'])) {
            // If data is directly an array of payment methods
            $paymentMethods = $data;
        }

        return $paymentMethods;
    }

    /**
     * Extract iframeUrl for CCIframe library, normalizing across possible key locations
     *
     * @param array $data
     * @return string
     */
    private function extractIframeUrl($data)
    {
        $iframeUrl = '';
        if (isset($data['iframeUrl'])) {
            $iframeUrl = $data['iframeUrl'];
        } elseif (isset($data['data']['iframeUrl'])) {
            $iframeUrl = $data['data']['iframeUrl'];
        } elseif (isset($data['ccIframeUrl'])) {
            $iframeUrl = $data['ccIframeUrl'];
        }

        return $iframeUrl;
    }

    /**
     * Extract integratorId for CCIframe initialization, normalizing across possible key locations
     *
     * @param array $data
     * @return string
     */
    private function extractIntegratorId($data)
    {
        $integratorId = '';
        if (isset($data['integratorId'])) {
            $integratorId = $data['integratorId'];
        } elseif (isset($data['data']['integratorId'])) {
            $integratorId = $data['data']['integratorId'];
        } elseif (isset($data['integrator_id'])) {
            $integratorId = $data['integrator_id'];
        }

        return $integratorId;
    }

    /**
     * Resolve saved cards from auth token and/or payment methods response
     *
     * @param string $authToken
     * @param array $data
     * @return array
     */
    private function resolveSavedCards($authToken, $data)
    {
        // Fetch saved cards if auth token is provided
        $savedCards = [];
        if (!empty($authToken)) {
            $savedCardsResponse = $this->getSavedCards($authToken);
            if ($savedCardsResponse['success'] && isset($savedCardsResponse['savedCards'])) {
                $savedCards = $savedCardsResponse['savedCards'];
            }
        }

        // Also check if saved cards are included in the payment methods response
        if (empty($savedCards)) {
            if (isset($data['savedCards']) && is_array($data['savedCards'])) {
                $savedCards = $data['savedCards'];
            } elseif (isset($data['data']['savedCards']) && is_array($data['data']['savedCards'])) {
                $savedCards = $data['data']['savedCards'];
            }
        }

        return $savedCards;
    }

    /**
     * Get payment status
     *
     * Checks the status of a payment by its ID using the /payments/getPaymentStatus GET API
     *
     * @param string $paymentId The payment ID to check
     * @param string $authToken User authentication token (optional)
     * @return array Response containing:
     *               - success: boolean indicating if the request was successful
     *               - status: payment status (PENDING, COMPLETED, FAILED, PAID)
     *               - data: full response data from API
     *               - error: error message if request failed
     */
    public function getPaymentStatus($paymentId, $authToken = '')
    {
        if (empty($paymentId)) {
            return [
                'success' => false,
                'error' => __('Payment ID is required'),
                'status' => ''
            ];
        }

        // Use the /payments/getPaymentStatus GET endpoint with paymentId as query parameter
        $endpoint = 'payments/getPaymentStatus?paymentId=' . urlencode($paymentId);
        $response = $this->transport->makeRequest($endpoint, 'GET', null, $authToken);

        if (!$response['success']) {
            return [
                'success' => false,
                'error' => $response['error'] ?? __('Failed to get payment status'),
                'status' => ''
            ];
        }

        $data = $response['data'] ?? [];

        // Extract status from response - check multiple possible locations
        $status = '';
        if (isset($data['status'])) {
            $status = $data['status'];
        } elseif (isset($data['data']['status'])) {
            $status = $data['data']['status'];
        } elseif (isset($data['paymentStatus'])) {
            $status = $data['paymentStatus'];
        } elseif (isset($data['payment']['status'])) {
            $status = $data['payment']['status'];
        }

        return [
            'success' => true,
            'status' => $status,
            'data' => $data
        ];
    }

    /**
     * Create payment link (initiate payment)
     *
     * @param array $paymentData
     * @param string|null $authToken
     * @return array
     */
    public function createPaymentLink($paymentData, $authToken = null)
    {
        $url = $this->transport->getApiBaseUrl() . '/payments/pay';

        if ($authToken) {
            $url .= '?api_token=' . urlencode($authToken);
        }

        $headers = [
            'Content-Type' => 'application/json',
            'Origin' => $this->transport->getOrigin(),
            'X-Auth-Merchant-Api-Key' => $this->transport->getApiKey()
        ];

        $this->transport->getCurl()->setHeaders($headers);

        try {
            $paymentData['apiKey'] = $this->transport->getApiKey();

            // Log the request for debugging (apiKey, api_token and card/BLIK secrets
            // redacted so they never reach the log file)
            $loggedPaymentData = $paymentData;
            $loggedPaymentData['apiKey'] = '***';
            if (isset($loggedPaymentData['paymentMethodData']) && is_array($loggedPaymentData['paymentMethodData'])) {
                foreach (['OTT', 'blikCode'] as $sensitiveKey) {
                    if (isset($loggedPaymentData['paymentMethodData'][$sensitiveKey])) {
                        $loggedPaymentData['paymentMethodData'][$sensitiveKey] = '***';
                    }
                }
            }
            $loggedUrl = preg_replace('/api_token=[^&]*/', 'api_token=***', $url);
            $this->transport->getLogger()->info('SimplyIN createPaymentLink request', [
                'url' => $loggedUrl,
                'paymentData' => $loggedPaymentData
            ]);

            $this->transport->getCurl()->post($url, json_encode($paymentData));
            $response = $this->transport->getCurl()->getBody();
            $statusCode = $this->transport->getCurl()->getStatus();

            // Log the raw response
            $this->transport->getLogger()->info('SimplyIN createPaymentLink response', [
                'statusCode' => $statusCode,
                'response' => $response
            ]);

            if ($statusCode >= 200 && $statusCode < 300) {
                $decoded = json_decode($response, true);

                // Log decoded response
                $this->transport->getLogger()->info('SimplyIN createPaymentLink decoded', [
                    'decoded' => $decoded
                ]);

                return [
                    'success' => true,
                    'data' => $decoded
                ];
            } else {
                $decoded = json_decode($response, true);
                return [
                    'success' => false,
                    'error' => $decoded['message'] ?? ($decoded['error'] ?? __('Payment creation failed')),
                    'response' => $response
                ];
            }
        } catch (\Exception $e) {
            $this->transport->getLogger()->error('SimplyIN createPaymentLink exception', [
                'error' => $e->getMessage()
            ]);
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
}
