<?php

namespace PortalPeople\redsys\controllers\api;

use backend\components\Security;
use backend\models\Cartsbe;
use PortalPeople\redsys\models\Redsys;
use Yii;
use yii\rest\Controller;
use yii\web\BadRequestHttpException;
use yii\web\UnauthorizedHttpException;

class PaymentsController extends Controller
{
    public function behaviors()
    {
        $behaviors = parent::behaviors();

        if (isset($behaviors['authenticator'])) {
            $behaviors['authenticator']['except'] = [
                'options',
                'payment',
                'webhook',
                'return',
                'cancel',
                'app-redirect',
                'request',
                'hpp-form',
            ];
        }

        return $behaviors;
    }

    /**
     * Legacy alias for the historic `/v3/payments/redsys` return endpoint.
     */
    public function actionPayment()
    {
        return $this->actionReturn();
    }

    /**
     * Create a payment request (REST integration).
     */
    public function actionRequest()
    {
        $request = Yii::$app->request;
        $params = $this->sanitizeArray((array) $request->getBodyParams());

        try {
            $context = $this->resolvePaymentContext($params);
            $redsys = $this->buildRedsysModel($context['merchant_params'], $context['secret_key']);

            return [
                'status' => 'success',
                'endpoint' => $redsys->getRestUrl($context['production']),
                'payload' => json_decode($redsys->getPayload(), true),
            ];
        } catch (\Exception $e) {
            throw new BadRequestHttpException($e->getMessage());
        }
    }

    /**
     * Create an HPP form (Hosted Payment Page).
     */
    public function actionHppForm()
    {
        $request = Yii::$app->request;
        $params = $this->sanitizeArray((array) $request->getBodyParams());

        try {
            $context = $this->resolvePaymentContext($params);
            $redsys = $this->buildRedsysModel($context['merchant_params'], $context['secret_key']);

            return [
                'status' => 'success',
                'endpoint' => $redsys->getHppUrl($context['production']),
                'form' => $redsys->getHppForm($context['production']),
                'fields' => $redsys->getHppPayload($context['production']),
            ];
        } catch (\Exception $e) {
            throw new BadRequestHttpException($e->getMessage());
        }
    }

    /**
     * Browser return endpoint after a payment attempt.
     */
    public function actionReturn()
    {
        $request = Yii::$app->request;
        $params = $request->getBodyParams() ?? $request->get();

        return $this->processWebhook($params, 'return');
    }

    /**
     * Browser return endpoint after customer cancellation.
     */
    public function actionCancel()
    {
        $request = Yii::$app->request;
        $params = $request->getBodyParams() ?? $request->get();

        return $this->processWebhook($params, 'cancel');
    }

    /**
     * Server-to-server notification endpoint.
     */
    public function actionWebhook()
    {
        $request = Yii::$app->request;
        $params = (array) $request->getBodyParams();

        $this->processWebhook($params, 'server-notification');

        Yii::$app->response->statusCode = 200;

        return ['status' => 'received'];
    }

    /**
     * Mobile app redirect notification endpoint.
     */
    public function actionAppRedirect()
    {
        $request = Yii::$app->request;
        $params = $request->getBodyParams() ?? $request->get();

        return $this->processWebhook($params, 'app-redirect');
    }

    private function processWebhook($params, $type)
    {
        if (empty($params)) {
            throw new BadRequestHttpException('Empty webhook payload');
        }

        $params = $this->sanitizeArray((array) $params);

        $merchantParams = $params['Ds_MerchantParameters'] ?? null;
        $signature = $params['Ds_Signature'] ?? null;
        $signatureVersion = $params['Ds_SignatureVersion'] ?? null;

        if (!$merchantParams || !$signature || !$signatureVersion) {
            Yii::error('Webhook missing required fields', [
                'type' => $type,
                'params' => array_keys($params),
            ]);
            throw new BadRequestHttpException('Missing webhook signature fields');
        }

        try {
            Redsys::checkSignatures(
                $signature,
                $this->computeRedsysSignature($merchantParams)
            );
        } catch (\Exception $e) {
            Yii::error('Webhook signature verification failed: ' . $e->getMessage(), [
                'type' => $type,
                'merchantParams' => substr($merchantParams, 0, 50) . '...',
            ]);
            throw new UnauthorizedHttpException('Invalid webhook signature');
        }

        $decoded = json_decode(Redsys::base64_url_decode_safe($merchantParams), true);

        if (!$decoded) {
            throw new BadRequestHttpException('Invalid merchant parameters');
        }

        $order = $decoded['DS_MERCHANT_ORDER'] ?? null;
        $response = $decoded['Ds_Response'] ?? null;

        if (!$order || $response === null) {
            throw new BadRequestHttpException('Missing order or response code');
        }

        Yii::info("Webhook received: type={$type}, order={$order}, response={$response}", __METHOD__);

        $responseData = $this->getResponseStatus($response);

        return [
            'status' => 'success',
            'type' => $type,
            'order' => $order,
            'response_code' => $response,
            'response_status' => $responseData['status'],
            'response_message' => $responseData['message'],
            'decoded_params' => $this->maskSensitiveArray($decoded),
        ];
    }

    private function computeRedsysSignature($merchantParams)
    {
        $decoded = json_decode(Redsys::base64_url_decode_safe($merchantParams), true);
        $order = $decoded['DS_MERCHANT_ORDER'] ?? '';

        $settings = $this->getGatewaySettings();
        $hmacKey = empty($settings['secret_key']) ? Redsys::HMAC_KEY : $settings['secret_key'];

        return Redsys::createMerchantSignature($hmacKey, $merchantParams, $order);
    }

    private function getResponseStatus($code)
    {
        $codes = [
            '0000' => ['status' => 'approved', 'message' => 'Transaction approved'],
            '0001' => ['status' => 'pending', 'message' => 'Transaction pending'],
            '0002' => ['status' => 'pending', 'message' => 'Transaction pending - notification later'],
            '0100' => ['status' => 'error', 'message' => 'Transaction temporarily rejected'],
            '0101' => ['status' => 'error', 'message' => 'Merchant not configured'],
            '0102' => ['status' => 'error', 'message' => 'Merchant does not support this transaction type'],
            '0103' => ['status' => 'error', 'message' => 'Terminal does not exist'],
            '0104' => ['status' => 'error', 'message' => 'Transaction data error'],
            '0105' => ['status' => 'error', 'message' => 'Merchant does not allow SCA exemption'],
            '0200' => ['status' => 'declined', 'message' => 'Card temporarily blocked'],
            '0202' => ['status' => 'declined', 'message' => 'Expired card'],
            '0203' => ['status' => 'declined', 'message' => 'Card not configured'],
            '0204' => ['status' => 'declined', 'message' => 'Card rejected by issuer - insufficient funds'],
            '0205' => ['status' => 'declined', 'message' => 'Card rejected by issuer'],
            '0207' => ['status' => 'declined', 'message' => 'Issuer unavailable'],
            '0208' => ['status' => 'declined', 'message' => 'Card blocked - fraud detected'],
            '0209' => ['status' => 'declined', 'message' => 'Card blocked - merchant request'],
            '0600' => ['status' => 'error', 'message' => '3DS authentication failed'],
            '0601' => ['status' => 'error', 'message' => '3DS authentication error'],
            '0602' => ['status' => 'error', 'message' => '3DS authentication unavailable'],
            '0900' => ['status' => 'cancelled', 'message' => 'Transaction cancelled by customer'],
            '0901' => ['status' => 'cancelled', 'message' => 'Transaction cancelled by system'],
        ];

        return $codes[$code] ?? [
            'status' => 'unknown',
            'message' => "Unknown response code: {$code}",
        ];
    }

    private function buildRedsysModel($params, $secretKey = null)
    {
        if (
            empty($params['DS_MERCHANT_ORDER'])
            || empty($params['DS_MERCHANT_MERCHANTCODE'])
            || empty($params['DS_MERCHANT_AMOUNT'])
        ) {
            throw new BadRequestHttpException('Missing required payment parameters');
        }

        $redsys = new Redsys();
        $redsys->merchantSecretKey = $secretKey;
        $redsys->DS_MERCHANT_ORDER = $params['DS_MERCHANT_ORDER'];
        $redsys->DS_MERCHANT_MERCHANTCODE = $params['DS_MERCHANT_MERCHANTCODE'];
        $redsys->DS_MERCHANT_TERMINAL = $params['DS_MERCHANT_TERMINAL'] ?? '001';
        $redsys->DS_MERCHANT_CURRENCY = $params['DS_MERCHANT_CURRENCY'] ?? '978';
        $redsys->DS_MERCHANT_TRANSACTIONTYPE = $params['DS_MERCHANT_TRANSACTIONTYPE'] ?? '0';
        $redsys->DS_MERCHANT_AMOUNT = $params['DS_MERCHANT_AMOUNT'];

        if (!empty($params['DS_MERCHANT_IDENTIFIER'])) {
            $redsys->DS_MERCHANT_IDENTIFIER = $params['DS_MERCHANT_IDENTIFIER'];
        }
        if (!empty($params['DS_MERCHANT_COF_INI'])) {
            $redsys->DS_MERCHANT_COF_INI = $params['DS_MERCHANT_COF_INI'];
        }
        if (!empty($params['DS_MERCHANT_DIRECTPAYMENT'])) {
            $redsys->DS_MERCHANT_DIRECTPAYMENT = $params['DS_MERCHANT_DIRECTPAYMENT'];
        }
        if (!empty($params['DS_MERCHANT_EXCEP_SCA'])) {
            $redsys->DS_MERCHANT_EXCEP_SCA = $params['DS_MERCHANT_EXCEP_SCA'];
        }
        if (!empty($params['DS_MERCHANT_MERCHANTURL'])) {
            $redsys->DS_MERCHANT_MERCHANTURL = $params['DS_MERCHANT_MERCHANTURL'];
        }
        if (!empty($params['DS_MERCHANT_URLOK'])) {
            $redsys->DS_MERCHANT_URLOK = $params['DS_MERCHANT_URLOK'];
        }
        if (!empty($params['DS_MERCHANT_URLKO'])) {
            $redsys->DS_MERCHANT_URLKO = $params['DS_MERCHANT_URLKO'];
        }

        return $redsys;
    }

    private function resolvePaymentContext($params)
    {
        $settings = $this->getGatewaySettings();
        $cart = $this->findCartFromParams($params);

        if ($cart !== null) {
            $params['DS_MERCHANT_ORDER'] = $params['DS_MERCHANT_ORDER'] ?? $this->buildMerchantOrder($cart);
            $params['DS_MERCHANT_AMOUNT'] = $this->formatAmountForRedsys($cart->amountout);
        }

        $params['DS_MERCHANT_MERCHANTCODE'] = $params['DS_MERCHANT_MERCHANTCODE'] ?? ($settings['merchant_code'] ?? null);
        $params['DS_MERCHANT_TERMINAL'] = $params['DS_MERCHANT_TERMINAL'] ?? ($settings['terminal'] ?? '001');
        $params['DS_MERCHANT_CURRENCY'] = $params['DS_MERCHANT_CURRENCY'] ?? ($settings['currency'] ?? '978');
        $params['DS_MERCHANT_TRANSACTIONTYPE'] = $params['DS_MERCHANT_TRANSACTIONTYPE'] ?? ($settings['transaction_type'] ?? '0');
        $params['DS_MERCHANT_MERCHANTURL'] = !empty($params['DS_MERCHANT_MERCHANTURL'])
            ? $params['DS_MERCHANT_MERCHANTURL']
            : (!empty($settings['notification_url']) ? $settings['notification_url'] : $this->buildCallbackUrl('webhook'));
        $params['DS_MERCHANT_URLOK'] = !empty($params['DS_MERCHANT_URLOK'])
            ? $params['DS_MERCHANT_URLOK']
            : (!empty($settings['success_url']) ? $settings['success_url'] : $this->buildCallbackUrl('return'));
        $params['DS_MERCHANT_URLKO'] = !empty($params['DS_MERCHANT_URLKO'])
            ? $params['DS_MERCHANT_URLKO']
            : (!empty($settings['error_url']) ? $settings['error_url'] : $this->buildCallbackUrl('cancel'));

        if (
            empty($params['DS_MERCHANT_ORDER'])
            || empty($params['DS_MERCHANT_MERCHANTCODE'])
            || empty($params['DS_MERCHANT_AMOUNT'])
        ) {
            throw new BadRequestHttpException('Missing required payment parameters');
        }

        return [
            'merchant_params' => $params,
            'secret_key' => $settings['secret_key'] ?? null,
            'production' => $this->isProductionRequest($params, $settings),
        ];
    }

    private function getGatewaySettings()
    {
        $legacy = Yii::$app->params['paygate'] ?? [];
        $redsys = Yii::$app->params['redsys'] ?? [];
        $settings = $legacy;

        foreach ([
            'enabled',
            'environment',
            'integration_mode',
            'merchant_code',
            'terminal',
            'currency',
            'transaction_type',
            'secret_key',
            'auto_capture',
            'debug',
            'notification_url',
            'success_url',
            'error_url',
        ] as $key) {
            if (array_key_exists($key, $redsys) && $redsys[$key] !== null && $redsys[$key] !== '') {
                $settings[$key] = $redsys[$key];
            }
        }

        $settings['environment'] = $settings['environment'] ?? 'production';
        $settings['integration_mode'] = $settings['integration_mode'] ?? 'rest';
        $settings['secret_key'] = Security::decryptSensitiveData($settings['secret_key'] ?? '');

        return $settings;
    }

    private function findCartFromParams($params)
    {
        $cartId = $this->extractCartId($params);

        if ($cartId === null) {
            return null;
        }

        $cart = Cartsbe::findOne($cartId);

        if ($cart === null) {
            throw new BadRequestHttpException("Cart not found: {$cartId}");
        }

        return $cart;
    }

    private function extractCartId($params)
    {
        foreach (['cartid', 'cart_id', 'cartId', 'cartsbe_id', 'cartsbeId'] as $key) {
            if (array_key_exists($key, $params) && $params[$key] !== '' && $params[$key] !== null) {
                return $this->normalizeCartId($params[$key]);
            }
        }

        if (empty($params['DS_MERCHANT_ORDER']) && empty($params['DS_MERCHANT_AMOUNT']) && array_key_exists('id', $params)) {
            return $this->normalizeCartId($params['id']);
        }

        return null;
    }

    private function normalizeCartId($cartId)
    {
        if (!is_scalar($cartId) || !is_numeric((string) $cartId) || (int) $cartId <= 0) {
            throw new BadRequestHttpException('Invalid cart ID');
        }

        return (int) $cartId;
    }

    private function buildMerchantOrder(Cartsbe $cart)
    {
        foreach ([$cart->ordernumber, $cart->orderref] as $candidate) {
            $candidate = strtoupper((string) preg_replace('/[^A-Z0-9]/i', '', (string) $candidate));

            if (strlen($candidate) >= 4) {
                return substr($candidate, 0, 12);
            }
        }

        return str_pad((string) $cart->id, 12, '0', STR_PAD_LEFT);
    }

    private function formatAmountForRedsys($amount)
    {
        $normalized = is_string($amount) ? str_replace(',', '.', trim($amount)) : $amount;
        $minorUnits = (int) round((float) $normalized * 100);

        if ($minorUnits <= 0) {
            throw new BadRequestHttpException('Cart has no outstanding balance to collect');
        }

        return (string) $minorUnits;
    }

    private function isProductionRequest($params, $settings)
    {
        if (array_key_exists('production', $params)) {
            return $this->toBool($params['production']);
        }

        return ($settings['environment'] ?? 'production') === 'production';
    }

    private function buildCallbackUrl($action)
    {
        $request = Yii::$app->request;
        $baseUrl = rtrim($request->hostInfo . $request->baseUrl, '/');

        return $baseUrl . '/v3/payments/' . ltrim($action, '/');
    }

    private function toBool($value)
    {
        if (is_bool($value)) {
            return $value;
        }

        if (is_numeric($value)) {
            return (int) $value === 1;
        }

        $normalized = strtolower(trim((string) $value));

        if (in_array($normalized, ['1', 'true', 'yes', 'on', 'production'], true)) {
            return true;
        }

        if (in_array($normalized, ['0', 'false', 'no', 'off', 'sandbox'], true)) {
            return false;
        }

        return false;
    }

    private function sanitizeArray($arr)
    {
        if (!is_array($arr)) {
            return $arr;
        }

        foreach ($arr as $key => $value) {
            if (is_string($value)) {
                $arr[$key] = trim(strip_tags($value));
            } elseif (is_array($value)) {
                $arr[$key] = $this->sanitizeArray($value);
            }
        }

        return $arr;
    }

    private function maskSensitiveArray($data)
    {
        $sensitiveKeys = [
            'password',
            'client_secret',
            'access_token',
            'refresh_token',
            'authorization',
            'authorization_header',
            'secret_key',
            'api_key',
            'Ds_Signature',
            'Ds_MerchantParameters',
        ];

        if (!is_array($data)) {
            return $data;
        }

        foreach ($data as $key => $value) {
            if (is_array($value)) {
                $data[$key] = $this->maskSensitiveArray($value);
            } elseif (in_array($key, $sensitiveKeys, true)) {
                $data[$key] = '***MASKED***';
            }
        }

        return $data;
    }
}