package tron

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"math/big"
	"strings"

	"github.com/tech/sendico/gateway/tron/shared"
	"github.com/tech/sendico/pkg/merrors"
)

const tronHexPrefix = "0x"

var base58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")

func normalizeAddress(address string) (string, error) {
	trimmed := strings.TrimSpace(address)
	if trimmed == "" {
		return "", merrors.InvalidArgument("address is required")
	}
	if strings.HasPrefix(trimmed, tronHexPrefix) || isHexString(trimmed) {
		return hexToBase58(trimmed)
	}
	decoded, err := base58Decode(trimmed)
	if err != nil {
		return "", err
	}
	if err := validateChecksum(decoded); err != nil {
		return "", err
	}
	return base58Encode(decoded), nil
}

func rpcAddress(address string) (string, error) {
	trimmed := strings.TrimSpace(address)
	if trimmed == "" {
		return "", merrors.InvalidArgument("address is required")
	}
	if strings.HasPrefix(trimmed, tronHexPrefix) || isHexString(trimmed) {
		return normalizeHexRPC(trimmed)
	}
	return shared.TronBase58ToHex(trimmed)
}

func hexToBase58(address string) (string, error) {
	bytesAddr, err := parseHexAddress(address)
	if err != nil {
		return "", err
	}
	payload := append(bytesAddr, checksum(bytesAddr)...)
	return base58Encode(payload), nil
}

func parseHexAddress(address string) ([]byte, error) {
	trimmed := strings.TrimPrefix(strings.TrimSpace(address), tronHexPrefix)
	if trimmed == "" {
		return nil, merrors.InvalidArgument("address is required")
	}
	if len(trimmed)%2 == 1 {
		trimmed = "0" + trimmed
	}
	decoded, err := hex.DecodeString(trimmed)
	if err != nil {
		return nil, merrors.InvalidArgument("invalid hex address")
	}
	switch len(decoded) {
	case 20:
		prefixed := make([]byte, 21)
		prefixed[0] = 0x41
		copy(prefixed[1:], decoded)
		return prefixed, nil
	case 21:
		if decoded[0] != 0x41 {
			return nil, merrors.InvalidArgument("invalid tron address prefix")
		}
		return decoded, nil
	default:
		return nil, merrors.InvalidArgument(fmt.Sprintf("invalid tron address length %d", len(decoded)))
	}
}

func normalizeHexRPC(address string) (string, error) {
	decoded, err := parseHexAddress(address)
	if err != nil {
		return "", err
	}
	return tronHexPrefix + hex.EncodeToString(decoded[1:21]), nil
}

func validateChecksum(decoded []byte) error {
	if len(decoded) != 25 {
		return merrors.InvalidArgument("invalid tron address length")
	}
	payload := decoded[:21]
	expected := checksum(payload)
	if !bytes.Equal(expected, decoded[21:]) {
		return merrors.InvalidArgument("invalid tron address checksum")
	}
	if payload[0] != 0x41 {
		return merrors.InvalidArgument("invalid tron address prefix")
	}
	return nil
}

func checksum(payload []byte) []byte {
	first := sha256.Sum256(payload)
	second := sha256.Sum256(first[:])
	return second[:4]
}

func base58Encode(input []byte) string {
	if len(input) == 0 {
		return ""
	}
	x := new(big.Int).SetBytes(input)
	base := big.NewInt(58)
	zero := big.NewInt(0)
	mod := new(big.Int)

	encoded := make([]byte, 0, len(input))
	for x.Cmp(zero) > 0 {
		x.DivMod(x, base, mod)
		encoded = append(encoded, base58Alphabet[mod.Int64()])
	}
	for _, b := range input {
		if b != 0 {
			break
		}
		encoded = append(encoded, base58Alphabet[0])
	}
	reverse(encoded)
	return string(encoded)
}

func base58Decode(input string) ([]byte, error) {
	result := big.NewInt(0)
	base := big.NewInt(58)

	for i := 0; i < len(input); i++ {
		idx := bytes.IndexByte(base58Alphabet, input[i])
		if idx < 0 {
			return nil, merrors.InvalidArgument("invalid base58 address")
		}
		result.Mul(result, base)
		result.Add(result, big.NewInt(int64(idx)))
	}

	decoded := result.Bytes()
	zeroCount := 0
	for zeroCount < len(input) && input[zeroCount] == base58Alphabet[0] {
		zeroCount++
	}
	if zeroCount > 0 {
		decoded = append(make([]byte, zeroCount), decoded...)
	}
	return decoded, nil
}

func reverse(data []byte) {
	for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
		data[i], data[j] = data[j], data[i]
	}
}

func isHexString(value string) bool {
	trimmed := strings.TrimPrefix(strings.TrimSpace(value), tronHexPrefix)
	if trimmed == "" {
		return false
	}
	for _, r := range trimmed {
		switch {
		case r >= '0' && r <= '9':
		case r >= 'a' && r <= 'f':
		case r >= 'A' && r <= 'F':
		default:
			return false
		}
	}
	return true
}
