<?php
namespace Bot\Services;

use Bot\Config;
use Bot\Database;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use PDO;

/**
 * Polls configured blockchains (Alchemy for EVM, TronGrid for TRON)
 * to detect inbound transfers into user wallets and credits balances.
 *
 * This service is designed to run from the CLI:
 * php bot.php watch:deposits
 */
class DepositWatcherService
{
    private PDO $pdo;
    private BalanceService $balances;
    private Client $http;
    private array $tokens;
    private int $lookbackBlocks;
    private string $alchemyUrl;
    private string $tronBase;
    private string $tronKey;

    public function __construct()
    {
        $config = Config::get();
        $this->pdo = Database::connection();
        $this->balances = new BalanceService();
        $this->http = new Client(['timeout' => 15]);
        $this->tokens = $config->getValue('watched_tokens', []);
        $this->lookbackBlocks = max(50, (int) $config->getValue('watch_lookback_blocks', 2000));
        $this->alchemyUrl = (string) $config->getValue('alchemy_api_url', '');
        $this->tronBase = rtrim((string) $config->getValue('trongrid_api_base', 'https://api.trongrid.io'), '/');
        $this->tronKey = (string) $config->getValue('trongrid_api_key', '');
    }

    public function syncAll(): int
    {
        $count = 0;
        if ($this->alchemyUrl !== '') {
            $count += $this->syncEvm('ERC20');
        }
        if ($this->tronBase !== '' && $this->supportsNetwork('TRC20')) {
            $count += $this->syncTron();
        }
        return $count;
    }

    private function syncEvm(string $network): int
    {
        $rpcUrl = $this->alchemyUrl;
        $wallets = $this->getWallets($network);
        if (!$wallets) {
            return 0;
        }
        $latest = $this->getLatestBlock($rpcUrl);
        if ($latest === null) {
            return 0;
        }
        $fromBlock = max($latest - $this->lookbackBlocks, 0);
        $fromHex = '0x' . dechex($fromBlock);
        $processed = 0;
        foreach ($this->tokens as $symbol => $tokenCfg) {
            $tokenAddress = $tokenCfg[$network] ?? '';
            if ($tokenAddress === '') {
                continue;
            }
            $decimals = (int) ($tokenCfg['decimals'] ?? 6);
            foreach ($wallets as $wallet) {
                $logs = $this->fetchEvmLogs($rpcUrl, $tokenAddress, $wallet['address'], $fromHex);
                foreach ($logs as $log) {
                    $txHash = strtolower($log['transactionHash'] ?? '');
                    if ($txHash === '' || $this->hasTransaction($wallet['id'], $txHash)) {
                        continue;
                    }
                    $rawValue = $log['data'] ?? '0x0';
                    $amount = $this->scaleAmount($this->hexToDecimal($rawValue), $decimals);
                    if ($amount === '0') {
                        continue;
                    }
                    $meta = [
                        'network'     => $network,
                        'blockNumber' => $log['blockNumber'] ?? '',
                        'logIndex'    => $log['logIndex'] ?? '',
                    ];
                    $this->recordDeposit($wallet, strtoupper($symbol), $amount, $txHash, $network, $meta);
                    $processed++;
                }
            }
        }
        return $processed;
    }

    private function syncTron(): int
    {
        $wallets = $this->getWallets('TRC20');
        if (!$wallets || !$this->tokens) {
            return 0;
        }
        $processed = 0;
        foreach ($this->tokens as $symbol => $tokenCfg) {
            $contract = $tokenCfg['TRC20'] ?? '';
            if ($contract === '') {
                continue;
            }
            $decimals = (int) ($tokenCfg['decimals'] ?? 6);
            foreach ($wallets as $wallet) {
                $txs = $this->fetchTronTransfers($wallet['address'], $contract);
                foreach ($txs as $tx) {
                    $hash = strtolower($tx['transaction_id'] ?? '');
                    if ($hash === '' || $this->hasTransaction($wallet['id'], $hash)) {
                        continue;
                    }
                    $rawValue = $tx['value'] ?? '0';
                    $amount = $this->scaleAmount($rawValue, $decimals);
                    if ($amount === '0') {
                        continue;
                    }
                    $meta = [
                        'network' => 'TRC20',
                        'block'   => $tx['block_timestamp'] ?? '',
                    ];
                    $this->recordDeposit($wallet, strtoupper($symbol), $amount, $hash, 'TRC20', $meta);
                    $processed++;
                }
            }
        }
        return $processed;
    }

    /**
     * @return array<int,array{id:int,user_id:int,address:string}>
     */
    private function getWallets(string $network): array
    {
        $stmt = $this->pdo->prepare('SELECT id, user_id, address FROM wallets WHERE network = ?');
        $stmt->execute([strtoupper($network)]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function fetchEvmLogs(string $rpcUrl, string $tokenAddress, string $toAddress, string $fromBlock): array
    {
        $topicTransfer = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
        $topicTo = '0x' . str_pad(strtolower(ltrim($toAddress, '0x')), 64, '0', STR_PAD_LEFT);
        $payload = [
            'jsonrpc' => '2.0',
            'id'      => 1,
            'method'  => 'eth_getLogs',
            'params'  => [[
                'fromBlock' => $fromBlock,
                'toBlock'   => 'latest',
                'address'   => $tokenAddress,
                'topics'    => [$topicTransfer, null, $topicTo],
            ]],
        ];
        try {
            $response = $this->http->post($rpcUrl, ['json' => $payload]);
            $result = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
            return $result['result'] ?? [];
        } catch (\Throwable $e) {
            return [];
        }
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    private function fetchTronTransfers(string $address, string $contract): array
    {
        $url = sprintf('%s/v1/accounts/%s/transactions/trc20', $this->tronBase, $address);
        $query = [
            'only_to'         => 'true',
            'limit'           => 50,
            'contract_address'=> $contract,
        ];
        $headers = $this->tronKey ? ['TRON-PRO-API-KEY' => $this->tronKey] : [];
        try {
            $response = $this->http->get($url, ['query' => $query, 'headers' => $headers]);
            $result = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
            return $result['data'] ?? [];
        } catch (GuzzleException|\JsonException $e) {
            return [];
        }
    }

    private function recordDeposit(array $wallet, string $currency, string $amount, string $txid, string $network, array $meta): void
    {
        $stmt = $this->pdo->prepare(
            "INSERT INTO transactions (wallet_id, txid, network, currency, amount, status, created_at)
             VALUES (:wallet, :txid, :network, :currency, :amount, 'confirmed', NOW())"
        );
        $stmt->execute([
            ':wallet'   => $wallet['id'],
            ':txid'     => $txid,
            ':network'  => $network,
            ':currency' => $currency,
            ':amount'   => $amount,
        ]);
        $this->balances->credit(
            (int) $wallet['user_id'],
            $currency,
            $amount,
            'deposit_confirmed',
            $txid,
            $meta,
            (int) $wallet['id']
        );
    }

    private function hasTransaction(int $walletId, string $txid): bool
    {
        $stmt = $this->pdo->prepare(
            'SELECT 1 FROM transactions WHERE wallet_id = ? AND txid = ? LIMIT 1'
        );
        $stmt->execute([$walletId, $txid]);
        return (bool) $stmt->fetchColumn();
    }

    private function getLatestBlock(string $rpcUrl): ?int
    {
        $payload = [
            'jsonrpc' => '2.0',
            'id'      => 99,
            'method'  => 'eth_blockNumber',
            'params'  => [],
        ];
        try {
            $response = $this->http->post($rpcUrl, ['json' => $payload]);
            $result = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
            if (!isset($result['result'])) {
                return null;
            }
            return hexdec($result['result']);
        } catch (\Throwable $e) {
            return null;
        }
    }

    private function supportsNetwork(string $network): bool
    {
        foreach ($this->tokens as $cfg) {
            if (!empty($cfg[$network])) {
                return true;
            }
        }
        return false;
    }

    private function hexToDecimal(string $hex): string
    {
        $hex = strtolower(ltrim($hex, '0x'));
        if ($hex === '') {
            return '0';
        }
        $decimal = '0';
        foreach (str_split($hex) as $char) {
            $decimal = $this->stringMultiply($decimal, 16);
            $decimal = $this->stringAdd($decimal, (string) hexdec($char));
        }
        return ltrim($decimal, '0') ?: '0';
    }

    private function scaleAmount(string $value, int $decimals): string
    {
        if ($decimals <= 0) {
            return $value;
        }
        $value = ltrim($value, '0');
        if ($value === '') {
            return '0';
        }
        if (strlen($value) <= $decimals) {
            $value = str_pad($value, $decimals + 1, '0', STR_PAD_LEFT);
        }
        $intPart = substr($value, 0, -$decimals);
        $fracPart = substr($value, -$decimals);
        $intPart = ltrim($intPart, '0');
        $fracPart = rtrim($fracPart, '0');
        $intPart = $intPart === '' ? '0' : $intPart;
        return $fracPart === '' ? $intPart : $intPart . '.' . $fracPart;
    }

    private function stringAdd(string $left, string $right): string
    {
        $left = strrev($left);
        $right = strrev($right);
        $len = max(strlen($left), strlen($right));
        $carry = 0;
        $result = '';
        for ($i = 0; $i < $len; $i++) {
            $a = $left[$i] ?? '0';
            $b = $right[$i] ?? '0';
            $sum = (int) $a + (int) $b + $carry;
            $result .= (string) ($sum % 10);
            $carry = intdiv($sum, 10);
        }
        if ($carry > 0) {
            $result .= (string) $carry;
        }
        return strrev($result);
    }

    private function stringMultiply(string $value, int $multiplier): string
    {
        $value = strrev($value);
        $carry = 0;
        $result = '';
        for ($i = 0, $len = strlen($value); $i < $len; $i++) {
            $product = ((int) $value[$i]) * $multiplier + $carry;
            $result .= (string) ($product % 10);
            $carry = intdiv($product, 10);
        }
        while ($carry > 0) {
            $result .= (string) ($carry % 10);
            $carry = intdiv($carry, 10);
        }
        $result = strrev($result);
        return ltrim($result, '0') ?: '0';
    }
}
