<?php
namespace Bot;

use Sop\CryptoTypes\Asymmetric\EC\ECPrivateKey;
use Sop\CryptoEncoding\PEM;
use kornrunner\Keccak;
use Trx\TrxClient;
use PDO;
use Bot\Services\EncryptionService;

/**
 * WalletService encapsulates operations for creating and retrieving user
 * cryptocurrency wallets across supported networks. It leverages external
 * libraries for Ethereum/BSC (ERC20/BEP20) and Tron (TRC20) address
 * generation. Generated private keys are persisted in the database for
 * subsequent transaction signing. In a production environment, sensitive
 * keys should be encrypted at rest.
 */
class WalletService
{
    private PDO $pdo;
    private EncryptionService $encryption;

    public function __construct()
    {
        $this->pdo = Database::connection();
        $this->encryption = new EncryptionService();
    }

    /**
     * Generate a new wallet for the given user and network and persist it
     * to the database.
     *
     * @param int    $userId  ID of the user owning the wallet
     * @param string $network Network identifier (ERC20, BEP20, TRC20)
     *
     * @return array{ id:int, address:string, private_key:string, mnemonic:?string }
     */
    public function createWallet(int $userId, string $network): array
    {
        $address  = '';
        $private  = '';
        $mnemonic = null;
        $network  = strtoupper($network);

        $config = Config::get();

        if ($network === 'TRC20') {
            // Use TronClient to generate a new TRON wallet. TronGrid API key
            // should be provided via the TRONGRID_API_KEY environment variable.
            $apiKey = $config->getValue('trongrid_api_key');
            $tron = new TrxClient($apiKey);
            $wallet = $tron->wallet()->create();
            $address = $wallet->getAddress();
            $private = $wallet->getPrivateKey();
        } else {
            // For ERC20 and BEP20 networks we manually generate a secp256k1 key
            // pair using OpenSSL and derive the Ethereum/BSC address by
            // computing the Keccak‑256 hash of the uncompressed public key.  See
            // QuickNode docs【432740381867074†L240-L289】【432740381867074†L312-L321】 for details.
            $opensslOptions = [
                'private_key_type' => OPENSSL_KEYTYPE_EC,
                'curve_name'       => 'secp256k1',
            ];
            $opensslConfigPath = (string) $config->getValue('openssl_config_path', '');
            if ($opensslConfigPath !== '') {
                if (!is_file($opensslConfigPath)) {
                    throw new \RuntimeException('Configured OpenSSL config not found: ' . $opensslConfigPath);
                }
                $opensslOptions['config'] = $opensslConfigPath;
            }
            $res = openssl_pkey_new($opensslOptions);
            if (!$res) {
                throw new \RuntimeException('Failed to generate secp256k1 key: ' . (openssl_error_string() ?: 'unknown OpenSSL error'));
            }
            // Export the private key PEM. On Windows OpenSSL requires the config
            // path for both generation and export operations.
            $exportOptions = [];
            if (isset($opensslOptions['config'])) {
                $exportOptions['config'] = $opensslOptions['config'];
            }
            if (!openssl_pkey_export($res, $privKeyPem, null, $exportOptions)) {
                throw new \RuntimeException('Failed to export private key: ' . (openssl_error_string() ?: 'unknown OpenSSL error'));
            }
            // Parse ASN.1 to extract raw keys
            $ecPriv  = ECPrivateKey::fromPEM(PEM::fromString($privKeyPem));
            $seq     = $ecPriv->toASN1();
            // Private key: 32 bytes
            $privHex = bin2hex($seq->at(1)->asOctetString()->string());
            // Public key (uncompressed): 0x04 + x + y
            $pubHex  = bin2hex($seq->at(3)->asTagged()->asExplicit()->asBitString()->string());
            // Remove the 0x04 prefix
            $pubHexNoPrefix = substr($pubHex, 2);
            // Keccak‑256 hash of the uncompressed public key
            $hash = Keccak::hash(hex2bin($pubHexNoPrefix), 256);
            // Take the last 20 bytes (40 hex chars) as the address
            $address = '0x' . substr($hash, -40);
            // Prefix private key with 0x for consistency
            $private = '0x' . $privHex;
            // No mnemonic is generated in this implementation
            $mnemonic = null;
        }
        $encrypted = $this->encryption->encrypt($private);
        $stmt = $this->pdo->prepare(
            "INSERT INTO wallets (user_id, network, address, private_key_encrypted, mnemonic, created_at)
             VALUES (:user_id, :network, :address, :private_key, :mnemonic, NOW())
             ON DUPLICATE KEY UPDATE address = VALUES(address), private_key_encrypted = VALUES(private_key_encrypted),
             mnemonic = VALUES(mnemonic), created_at = VALUES(created_at)"
        );
        $stmt->execute([
            ':user_id'    => $userId,
            ':network'    => $network,
            ':address'    => $address,
            ':private_key'=> $encrypted,
            ':mnemonic'   => $mnemonic,
        ]);
        $id = (int) $this->pdo->lastInsertId();
        return [
            'id'          => $id,
            'address'     => $address,
            'private_key' => $private,
            'mnemonic'    => $mnemonic,
        ];
    }

    /**
     * Retrieve the most recently created wallet for a user on the specified
     * network. Returns null if no wallet exists.
     *
     * @param int    $userId
     * @param string $network
     *
     * @return array|null
     */
    public function getWallet(int $userId, string $network): ?array
    {
        $stmt = $this->pdo->prepare(
            "SELECT id, user_id, network, address, created_at FROM wallets WHERE user_id = ? AND network = ? LIMIT 1"
        );
        $stmt->execute([$userId, strtoupper($network)]);
        $wallet = $stmt->fetch();
        return $wallet ?: null;
    }
}
