package main

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"math"
	"math/big"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
)

type DeBankToken struct {
	Symbol   string  `json:"symbol"`
	Chain    string  `json:"chain"`
	Balance  string  `json:"balance"`
	Decimals int     `json:"decimals"`
	Price    float64 `json:"price"`
	Amount   float64 `json:"amount"`
}

type DeBankResponse struct {
	Data []DeBankToken `json:"data"`
}

var networks = map[string]string{
	"eth":   "Ethereum",
	"bsc":   "BSC",
	"avax":  "Avalanche",
	"matic": "Polygon",
	"arb":   "Arbitrum",
	"ftm":   "Fantom",
	"op":    "Optimism",
	"cro":   "Cronos",
}

type DebankClient struct {
	account *BrowserUID
	nonce   *DebankNonce
	signer  *DebankSigner
	client  *http.Client
}

func NewDebankClient(transport *http.Transport) *DebankClient {
	if transport == nil {
		transport = &http.Transport{}
	}

	account := makeBrowserUID()
	return &DebankClient{
		account: &account,
		nonce:   NewDebankNonce(),
		signer:  &DebankSigner{},
		client: &http.Client{
			Transport: transport,
			Timeout:   30 * time.Second,
		},
	}
}

func (d *DebankClient) Get(pathname string, params map[string]string) ([]byte, error) {
	return d.request("GET", pathname, params)
}

func (d *DebankClient) request(method, pathname string, params map[string]string) ([]byte, error) {
	values := url.Values{}
	for k, v := range params {
		values.Add(k, v)
	}

	keys := make([]string, 0, len(values))
	for k := range values {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
		keys[i], keys[j] = keys[j], keys[i]
	}

	var queryParts []string
	for _, k := range keys {
		queryParts = append(queryParts, url.QueryEscape(k)+"="+url.QueryEscape(values.Get(k)))
	}
	queryString := strings.Join(queryParts, "&")

	apiURL := "https://api.debank.com" + pathname
	if queryString != "" {
		apiURL += "?" + queryString
	}

	req, err := http.NewRequest(method, apiURL, nil)
	if err != nil {
		return nil, err
	}

	headers := d.makeHeaders(method, pathname, queryString)
	for key, value := range headers {
		req.Header.Set(key, value)
	}

	resp, err := d.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("debank API error: status %d, body: %s", resp.StatusCode, string(body))
	}

	var errorResp struct {
		ErrorCode int    `json:"error_code"`
		ErrorMsg  string `json:"error_msg"`
	}
	if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.ErrorCode != 0 {
		return nil, fmt.Errorf("%d: %s", errorResp.ErrorCode, errorResp.ErrorMsg)
	}

	return body, nil
}

func (d *DebankClient) makeHeaders(method, pathname, query string) map[string]string {
	if len(query) > 0 && query[0] == '?' {
		query = query[1:]
	}

	nonceStr := d.nonce.Next()
	ts := time.Now().Unix()
	signature := d.signer.Sign(method, pathname, query, nonceStr, ts)

	accountJSON, _ := json.Marshal(d.account)

	return map[string]string{
		"x-api-nonce":     "n_" + nonceStr,
		"x-api-sign":      signature,
		"x-api-ts":        strconv.FormatInt(ts, 10),
		"x-api-ver":       "v2",
		"source":          "web",
		"account":         string(accountJSON),
		"User-Agent":      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
		"Accept":          "*/*",
		"Accept-Language": "en-US,en;q=0.9",
		"Referer":         "https://debank.com/",
		"Origin":          "https://debank.com",
	}
}

type BrowserUID struct {
	RandomAt int64   `json:"random_at"`
	RandomID string  `json:"random_id"`
	UserAddr *string `json:"user_addr"`
}

type DebankNonce struct {
	abc   string
	local int64
}

func NewDebankNonce() *DebankNonce {
	return &DebankNonce{
		abc:   "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",
		local: 0,
	}
}

func (n *DebankNonce) pcg32() uint32 {
	const multiplier = 6364136223846793005
	const increment = 1

	n.local = n.local*multiplier + increment
	return uint32((uint64(n.local) >> 33) & 0xFFFFFFFF)
}

func (n *DebankNonce) Next() string {
	result := make([]byte, 40)
	for i := 0; i < 40; i++ {
		index := int(float64(n.pcg32()) / 2147483647.0 * 61.0)
		if index >= len(n.abc) {
			index = len(n.abc) - 1
		}
		result[i] = n.abc[index]
	}
	return string(result)
}

type DebankSigner struct{}

func (s *DebankSigner) Sign(method, pathname, query string, nonce string, ts int64) string {
	data1 := method + "\n" + pathname + "\n" + query
	hash1 := sha256.Sum256([]byte(data1))
	hash1Hex := hex.EncodeToString(hash1[:])

	data2 := "debank-api\nn_" + nonce + "\n" + strconv.FormatInt(ts, 10)
	hash2 := sha256.Sum256([]byte(data2))
	hash2Hex := hex.EncodeToString(hash2[:])

	xor1, xor2 := s.xor(hash2Hex)

	h1Data := xor1 + hash1Hex
	h1 := sha256.Sum256([]byte(h1Data))

	h2Data := append([]byte(xor2), h1[:]...)
	h2 := sha256.Sum256(h2Data)

	return hex.EncodeToString(h2[:])
}

func (s *DebankSigner) xor(hash string) (string, string) {
	rez1 := make([]byte, 64)
	rez2 := make([]byte, 64)

	for i := 0; i < 64; i++ {
		rez1[i] = hash[i] ^ 54
		rez2[i] = hash[i] ^ 92
	}

	return string(rez1), string(rez2)
}

func makeBrowserUID() BrowserUID {
	return BrowserUID{
		RandomAt: time.Now().Unix(),
		RandomID: strings.ReplaceAll(generateUUID(), "-", ""),
		UserAddr: nil,
	}
}

func generateUUID() string {
	b := make([]byte, 16)
	rand.Read(b)

	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

type DebankUser struct {
	ID   string `json:"id"`
	Desc struct {
		BornAt   int64   `json:"born_at"`
		USDValue float64 `json:"usd_value"`
	} `json:"desc"`
	Stats struct {
		USDValue float64 `json:"usd_value"`
	} `json:"stats"`
}

type DebankUserResponse struct {
	User DebankUser `json:"user"`
}

func (d *DebankClient) GetUser(address string) (*DebankUserResponse, error) {
	body, err := d.Get("/user", map[string]string{
		"id": address,
	})
	if err != nil {
		return nil, err
	}

	var wrapper struct {
		Data DebankUserResponse `json:"data"`
	}
	if err := json.Unmarshal(body, &wrapper); err != nil {
		return nil, err
	}

	return &wrapper.Data, nil
}

func getDeBankBalance(address string) (float64, string, error) {
	transport, err := getNextProxy()
	if err != nil {
		transport = &http.Transport{}
	}

	client := NewDebankClient(transport)

	userResp, err := client.GetUser(address)
	if err != nil {
		return 0, "", fmt.Errorf("debank API error: %v", err)
	}

	totalValue := userResp.User.Desc.USDValue
	if totalValue == 0 {
		totalValue = userResp.User.Stats.USDValue
	}

	if totalValue == 0 {
		return 0, "", nil
	}

	return totalValue, "", nil
}

func getBalanceValue(balance string, decimals int) float64 {
	if balance == "" {
		return 0
	}

	bal := new(big.Int)
	bal.SetString(balance, 10)

	if bal.Sign() == 0 {
		return 0
	}

	divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil))
	balFloat := new(big.Float).SetInt(bal)
	result := new(big.Float).Quo(balFloat, divisor)

	resultFloat, _ := result.Float64()
	return math.Round(resultFloat*100000) / 100000
}
