package com.crm.pay.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.crm.pay.dto.ucard.TxResult;
import com.crm.rely.backend.core.exception.ServiceException;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Locale;

@Slf4j
public class EncryptionHashQueryUtil {

    /* ================= API ================= */

    private static final String ETH_API = "https://eth.blockscout.com/api/v2/transactions/";
    private static final String ETC_API = "https://blockscout.com/etc/mainnet/api/v2/transactions/";
    private static final String POLYGON_API = "https://polygon.blockscout.com/api/v2/transactions/";
    private static final String TRON_API = "https://apilist.tronscanapi.com/api/transaction-info?hash=";
    private static final String SOL_API = "https://api.mainnet-beta.solana.com";

    /* ================= 合约地址 ================= */

    private static final String ETH_USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
    private static final String ETH_USDC = "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";

    private static final String ETC_USDT = "0x1953cab0E5bFa6D4a9BaD6E05fD46C1CC6527a5a";

    private static final String POLYGON_USDT = "0xC2132D05D31c914a87C6611C10748AaCbEFD1b2";
    private static final String POLYGON_USDC = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";

    private static final String TRC_USDT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
    private static final String TRC_USDC = "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8";

    private static final String SOL_USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";
    private static final String SOL_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

    public static void main(String[] args) throws Exception {
        String txHash = "c370e2f1b6ac1d9f7078282ddad20d70f3623a2eaa8481b498d82585de9d94ee";
        String chain = "tron";

        System.out.println(query(txHash, chain));
    }

    /* ================= 统一入口 ================= */

    public static TxResult query(String txHash, String chain) throws Exception {
        return query(txHash, chain, "usdt");
    }

    public static TxResult query(String txHash, String chain, String currency) throws Exception {
        if (Strings.isNullOrEmpty(currency)) {
            currency = "usdt";
        }

        switch (chain.toLowerCase(Locale.ROOT)) {
            case "ethereum":
                return evmTxQuery(ETH_API, txHash, ethContract(currency));
            case "ethereum-classic":
                return evmTxQuery(ETC_API, txHash, ETC_USDT);
            case "polygon":
                return evmTxQuery(POLYGON_API, txHash, polygonContract(currency));
            case "tron":
                return tronTxQuery(txHash, currency);
            case "solana":
                return solTxQuery(txHash, currency);
            default:
                throw new ServiceException("Unsupported chain: " + chain);
        }
    }

    /* ================= EVM ================= */

    private static TxResult evmTxQuery(String api, String txHash, String expectedContract) throws Exception {

        String json = get(api + txHash);
        JSONObject obj = JSONObject.parseObject(json);

        JSONArray transfers = obj.getJSONArray("token_transfers");
        if (transfers == null || transfers.isEmpty()) {
            throw new ServiceException("No token transfer");
        }

        for (Object o : transfers) {
            JSONObject t = (JSONObject) o;
            JSONObject token = t.getJSONObject("token");
            if (token == null) {
                continue;
            }

            String contract = token.getString("contract_address");
            if (contract == null) {
                contract = token.getString("address_hash");
            }
            if (!expectedContract.equalsIgnoreCase(contract)) {
                continue;
            }

            JSONObject total = t.getJSONObject("total");
            if (total == null) {
                continue;
            }

            BigDecimal amount = new BigDecimal(total.getString("value"))
                    .movePointLeft(total.getIntValue("decimals"));

            JSONObject fromObj = t.getJSONObject("from");
            JSONObject toObj = t.getJSONObject("to");

            String from = fromObj == null ? null : fromObj.getString("hash");
            String to = toObj == null ? null : toObj.getString("hash");

            String status = obj.getString("status");
            boolean success = !"failed".equalsIgnoreCase(status)
                    && !"error".equalsIgnoreCase(status);

            TxResult r = new TxResult();
            r.setTxHash(txHash);
            r.setFrom(from);
            r.setTo(to);
            r.setAmount(amount);
            r.setContractAddress(contract);
            r.setStatus(success ? "success" : "failed");
            r.setContent(json);

            String tsStr = obj.getString("timestamp");
            r.setTimestamp(parseTimestamp(tsStr));

            return r;
        }

        throw new ServiceException("Target token transfer not found");
    }

    /* ================= Tron ================= */

    private static TxResult tronTxQuery(String txHash, String currency) throws Exception {

        String json = get(TRON_API + txHash);
        JSONObject obj = JSONObject.parseObject(json);

        JSONArray arr = obj.getJSONArray("trc20TransferInfo");
        if (arr == null || arr.isEmpty()) {
            throw new ServiceException("No TRC20 transfer");
        }

        JSONObject t = arr.getJSONObject(0);
        String contract = t.getString("contract_address");
        String expect = currency.equalsIgnoreCase("usdc") ? TRC_USDC : TRC_USDT;

        if (!expect.equalsIgnoreCase(contract)) {
            throw new ServiceException("Invalid TRC20 contract");
        }

        BigDecimal amount = new BigDecimal(t.getString("amount_str"))
                .movePointLeft(t.getIntValue("decimals"));

        String ret = obj.getString("contractRet");

        TxResult r = new TxResult();
        r.setTxHash(txHash);
        r.setFrom(t.getString("from"));
        r.setTo(t.getString("to"));
        r.setAmount(amount);
        r.setContractAddress(contract);
        r.setTimestamp(obj.getLongValue("timestamp") / 1000);
        r.setStatus("SUCCESS".equalsIgnoreCase(ret) ? "success" : "failed");
        r.setContent(json);
        return r;
    }

    /* ================= Solana ================= */

    private static TxResult solTxQuery(String txHash, String currency) throws Exception {

        String mint = currency.equalsIgnoreCase("usdc") ? SOL_USDC : SOL_USDT;
        String json = solanaRpc(txHash);

        JSONObject result = JSONObject.parseObject(json).getJSONObject("result");
        JSONObject meta = result.getJSONObject("meta");

        JSONArray inner = meta.getJSONArray("innerInstructions");
        if (inner == null) {
            throw new ServiceException("No innerInstructions");
        }

        for (Object o : inner) {
            JSONArray inst = ((JSONObject) o).getJSONArray("instructions");
            for (Object i : inst) {
                JSONObject p = ((JSONObject) i).getJSONObject("parsed");
                if (p == null) {
                    continue;
                }

                JSONObject info = p.getJSONObject("info");
                if (!mint.equalsIgnoreCase(info.getString("mint"))) {
                    continue;
                }

                BigDecimal amount = new BigDecimal(
                        info.getJSONObject("tokenAmount").getString("amount")
                ).movePointLeft(info.getJSONObject("tokenAmount").getIntValue("decimals"));

                TxResult r = new TxResult();
                r.setTxHash(txHash);
                r.setFrom(info.getString("authority"));
                r.setTo(info.getString("destination"));
                r.setAmount(amount);
                r.setContractAddress(mint);
                r.setStatus(meta.get("err") == null ? "success" : "failed");
                r.setContent(json);

                Long blockTime = result.getLong("blockTime");
                if (blockTime != null) {
                    r.setTimestamp(blockTime);
                }

                return r;
            }
        }

        throw new ServiceException("No SPL transfer");
    }

    /* ================= 工具 ================= */

    private static long parseTimestamp(String ts) {
        try {
            return Instant.parse(ts).getEpochSecond();
        } catch (Exception e) {
            return Long.parseLong(ts.substring(0, 10));
        }
    }

    private static String ethContract(String c) {
        return c.equalsIgnoreCase("usdc") ? ETH_USDC : ETH_USDT;
    }

    private static String polygonContract(String c) {
        return c.equalsIgnoreCase("usdc") ? POLYGON_USDC : POLYGON_USDT;
    }

    private static String solanaRpc(String tx) throws Exception {

        JSONObject body = new JSONObject();
        body.put("jsonrpc", "2.0");
        body.put("id", 1);
        body.put("method", "getTransaction");
        body.put("params", new Object[]{tx, new JSONObject().fluentPut("encoding", "jsonParsed")});

        HttpURLConnection conn = (HttpURLConnection) new URL(SOL_API).openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        conn.getOutputStream().write(body.toJSONString().getBytes(StandardCharsets.UTF_8));

        return read(conn);
    }

    private static String get(String url) throws Exception {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestProperty("User-Agent", "Mozilla/5.0");
        return read(conn);
    }

    private static String read(HttpURLConnection conn) throws Exception {

        InputStream in = null;
        try {
            in = conn.getResponseCode() >= 400
                    ? conn.getErrorStream()
                    : conn.getInputStream();

            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
                byte[] buf = new byte[8192];
                int n;
                while ((n = in.read(buf)) != -1) {
                    out.write(buf, 0, n);
                }
                return new String(out.toByteArray(), StandardCharsets.UTF_8);
            }
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }
}