<?php

namespace App\Services;

use App\Util;
use Exception;

class PayPlus
{
    const PAYIN_CONFIG = 'PayPlus';
    const PAYOUT_CONFIG = 'PayPlusOut';

    protected $config;

    public function __construct(array $config = null)
    {
        if ($config === null) {
            $config = (new PayConfig())->getConfig(self::PAYIN_CONFIG) ?: [];
        }

        $this->config = $config;
    }

    public function getConfig()
    {
        return $this->config;
    }

    public function getPayoutService()
    {
        return new self((new PayConfig())->getConfig(self::PAYOUT_CONFIG) ?: []);
    }

    public function signPayoutPayload(array $payload)
    {
        return hash('sha256', $this->buildPayoutSignString($payload) . ($this->config['appKey'] ?? ''));
    }

    public function verifyPayoutSignature(array $payload, $signature)
    {
        return hash_equals($this->signPayoutPayload($payload), strtolower((string) $signature));
    }

    public function buildPayoutSignString(array $payload)
    {
        $flat = $this->flattenPayload($payload);
        ksort($flat);

        $items = [];
        foreach ($flat as $key => $value) {
            if ($value === null || (is_string($value) && trim($value) === '')) {
                continue;
            }

            if (is_bool($value)) {
                $value = $value ? 'true' : 'false';
            }

            $items[] = $key . '=' . $value;
        }

        return implode('&', $items);
    }

    public function flattenPayload(array $payload, $prefix = '')
    {
        $result = [];
        foreach ($payload as $key => $value) {
            if ($key === 'Authorization' || $key === 'sign') {
                continue;
            }

            $flatKey = $prefix === '' ? $key : $prefix . '_' . $key;
            if (is_array($value) && $this->isAssoc($value)) {
                $result += $this->flattenPayload($value, $flatKey);
                continue;
            }

            if (is_array($value)) {
                $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }

            $result[$flatKey] = $value;
        }

        return $result;
    }

    public function encryptComponentDelta($payload, $aesKey, $iv)
    {
        $tag = '';
        $cipherText = openssl_encrypt(
            $payload,
            'aes-256-gcm',
            hex2bin($aesKey),
            OPENSSL_RAW_DATA,
            hex2bin($iv),
            $tag,
            '',
            16
        );

        if ($cipherText === false) {
            throw new Exception('PayPlus AES encrypt failed.');
        }

        return base64_encode(hex2bin($iv) . $cipherText . $tag);
    }

    public function decryptComponentDelta($componentDelta, $aesKey, $iv)
    {
        $raw = base64_decode($componentDelta, true);
        if ($raw === false || strlen($raw) <= 28) {
            throw new Exception('PayPlus AES response is invalid.');
        }

        $cipherTextWithTag = substr($raw, 12);
        $tag = substr($cipherTextWithTag, -16);
        $cipherText = substr($cipherTextWithTag, 0, -16);

        $plainText = openssl_decrypt(
            $cipherText,
            'aes-256-gcm',
            hex2bin($aesKey),
            OPENSSL_RAW_DATA,
            hex2bin($iv),
            $tag
        );

        if ($plainText === false) {
            throw new Exception('PayPlus AES decrypt failed.');
        }

        return $plainText;
    }

    public function encryptPayinPayload(array $payload)
    {
        $aesKey = bin2hex(random_bytes(32));
        $iv = bin2hex(random_bytes(12));

        return [
            'aes_key' => $aesKey,
            'iv' => $iv,
            'body' => [
                'componentX' => $this->rsaEncrypt($aesKey),
                'componentY' => $this->rsaEncrypt($iv),
                'componentDelta' => $this->encryptComponentDelta(
                    json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                    $aesKey,
                    $iv
                ),
            ],
        ];
    }

    public function decryptPayinResponse(array $response, $aesKey, $iv)
    {
        $componentDelta = $response['data']['componentDelta'] ?? '';
        if ($componentDelta === '') {
            return [];
        }

        return json_decode($this->decryptComponentDelta($componentDelta, $aesKey, $iv), true) ?: [];
    }

    public function postPayin(array $payload)
    {
        return $this->postPayinPath('/up-apis/merchant/payment', $payload);
    }

    public function queryPayinOrder($platformOrderId, $orderId = '')
    {
        if ($platformOrderId) {
            $payload = [
                'platform_order_id' => (string) $platformOrderId,
            ];
        } else if ($orderId) {
            $payload = [
                'order_id' => (string) $orderId,
            ];
        } else {
            throw new Exception('Either platformOrderId or orderId must be provided for querying PayPlus order.');
        }
        
        Util::WriteLog('PayPlus', 'Query PayPlus order: ' . json_encode($payload));

        return $this->postPayinPath(
            $this->config['query_path'] ?? '/up-apis/merchant/payment/query',
            $payload
        );
    }

    protected function postPayinPath($path, array $payload)
    {
        $encrypted = $this->encryptPayinPayload($payload);
        $response = $this->curlJson(
            rtrim($this->config['apiUrl'] ?? '', '/') . $path,
            $encrypted['body'],
            $this->buildPayinHeaders()
        );

        $decoded = json_decode($response, true) ?: [];
        Util::WriteLog('PayPlus', 'PayPlus raw response: ' . json_encode($decoded));
        $componentDelta = $this->decryptPayinResponse($decoded, $encrypted['aes_key'], $encrypted['iv']);
        if (!empty($componentDelta)) {
            Util::WriteLog('PayPlus', 'PayPlus decrypted componentDelta: ' . json_encode($componentDelta));
        }
        $decoded['decryptedComponentDelta'] = $componentDelta;
        return $decoded;
    }

    public function postPayout(string $path, array $payload)
    {
        $payload['timestamp'] = $payload['timestamp'] ?? $this->milliseconds();
        $headers = [
            'Content-Type: application/json',
            'AppId: ' . ($this->config['appId'] ?? ''),
            'Authorization: ' . $this->signPayoutPayload($payload),
        ];

        $response = $this->curlJson(rtrim($this->config['apiUrl'] ?? '', '/') . $path, $payload, $headers);

        return json_decode($response, true) ?: [];
    }

    public function buildBeneficiaryPayload(array $user, $timestamp = null)
    {
        $userId = (string) ($user['user_id'] ?? $user['payee_id'] ?? 0);
        $name = trim((string) ($user['name'] ?? $user['user_name'] ?? ''));
        $names = preg_split('/\s+/', $name, 2);

        return [
            'payee_id' => $userId,
            'language' => $this->stringOrDefault($user['language'] ?? null, 'en'),
            'country' => strtoupper($this->stringOrDefault($user['country'] ?? null, 'US')),
            'phone' => $this->normalizePhone($user['phone'] ?? ''),
            'birthdate' => $this->stringOrDefault($user['birthdate'] ?? null, '1970-01-01'),
            'email' => $this->emailOrDefault($user['email'] ?? '', $userId),
            'first_name' => $this->stringOrDefault($user['first_name'] ?? ($names[0] ?? null), 'unknown'),
            'last_name' => $this->stringOrDefault($user['last_name'] ?? ($names[1] ?? null), 'user'),
            'zip' => $this->stringOrDefault($user['zip'] ?? null, '00000'),
            'city' => $this->stringOrDefault($user['city'] ?? null, 'unknown'),
            'state' => $this->stringOrDefault($user['state'] ?? null, 'NA'),
            'address' => $this->stringOrDefault($user['address'] ?? null, 'unknown'),
            'timestamp' => $timestamp ?? $this->milliseconds(),
        ];
    }

    protected function rsaEncrypt($value)
    {
        $publicKey = $this->config['publicKey'] ?? '';
        if ($publicKey === '') {
            throw new Exception('PayPlus public key is empty.');
        }

        if (!class_exists('\phpseclib\Crypt\RSA')) {
            throw new Exception('phpseclib is required for PayPlus RSA OAEP SHA-512.');
        }

        $rsa = new \phpseclib\Crypt\RSA();
        $rsa->setEncryptionMode(\phpseclib\Crypt\RSA::ENCRYPTION_OAEP);
        $rsa->setHash('sha512');
        $rsa->setMGFHash('sha512');
        $rsa->loadKey($this->normalizePublicKey($publicKey));

        $encrypted = $rsa->encrypt($value);
        if ($encrypted === false) {
            throw new Exception('PayPlus RSA encrypt failed.');
        }

        return base64_encode($encrypted);
    }

    protected function buildPayinHeaders()
    {
        return [
            'Content-Type: application/json',
            'x-api-key: ' . ($this->config['apiKey'] ?? ''),
            'x-client-id: ' . ($this->config['clientId'] ?? ''),
            'x-app-id: ' . ($this->config['appId'] ?? ''),
        ];
    }

    protected function curlJson($url, array $payload, array $headers)
    {
        $data = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, 20);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $result = curl_exec($ch);
        if (curl_errno($ch)) {
            $error = curl_error($ch);
            curl_close($ch);
            Util::WriteLog('PayPlus_error', 'CURL Error: ' . $error);
            throw new Exception($error);
        }

        curl_close($ch);

        return $result;
    }

    protected function normalizePublicKey($key)
    {
        if (strpos($key, 'BEGIN PUBLIC KEY') !== false) {
            return $key;
        }

        return "-----BEGIN PUBLIC KEY-----\n"
            . chunk_split($key, 64, "\n")
            . "-----END PUBLIC KEY-----";
    }

    protected function normalizePhone($phone)
    {
        $phone = trim((string) $phone);
        if ($phone === '') {
            return '+1-0000000000';
        }

        if (strpos($phone, '+') === 0) {
            return $phone;
        }

        return '+1-' . preg_replace('/\D+/', '', $phone);
    }

    protected function emailOrDefault($email, $userId)
    {
        $email = trim((string) $email);
        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $email;
        }

        return 'unknown' . $userId . '@example.com';
    }

    protected function stringOrDefault($value, $default)
    {
        $value = trim((string) $value);

        return $value === '' ? $default : $value;
    }

    protected function milliseconds()
    {
        return (int) round(microtime(true) * 1000);
    }

    private function isAssoc(array $value)
    {
        return array_keys($value) !== range(0, count($value) - 1);
    }
}
