package nym

import (
	"fmt"
	"math/big"
	"strings"
)

const (
	pubkeySize = 32
	// RecipientLen is the wire length of a nym-client Recipient: identity ||
	// encryption || gateway pubkeys, 32 bytes each.
	RecipientLen = 3 * pubkeySize
)

var base58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

// decodeBase58 decodes plain base58 (Bitcoin alphabet, no checksum), matching
// the Rust `bs58` crate defaults used by nym-sphinx for identity/encryption keys.
func decodeBase58(s string) ([]byte, error) {
	if s == "" {
		return nil, fmt.Errorf("empty base58 string")
	}

	num := new(big.Int)
	base := big.NewInt(58)
	for _, r := range s {
		idx := strings.IndexRune(base58Alphabet, r)
		if idx < 0 {
			return nil, fmt.Errorf("invalid base58 character %q", r)
		}
		num.Mul(num, base)
		num.Add(num, big.NewInt(int64(idx)))
	}

	decoded := num.Bytes()

	// Each leading '1' encodes a leading zero byte.
	leadingZeros := 0
	for _, r := range s {
		if r != '1' {
			break
		}
		leadingZeros++
	}

	out := make([]byte, leadingZeros+len(decoded))
	copy(out[leadingZeros:], decoded)
	return out, nil
}

// ParseRecipient converts a nym address string "identity.encryption@gateway"
// into the 96-byte wire encoding expected by the nym-client binary WS protocol
// (identity_pubkey || encryption_pubkey || gateway_identity_pubkey).
func ParseRecipient(addr string) ([]byte, error) {
	atParts := strings.Split(addr, "@")
	if len(atParts) != 2 {
		return nil, fmt.Errorf("nym address must contain exactly one '@': %q", addr)
	}
	clientHalf, gatewayHalf := atParts[0], atParts[1]

	dotParts := strings.Split(clientHalf, ".")
	if len(dotParts) != 2 {
		return nil, fmt.Errorf("nym address client part must contain exactly one '.': %q", addr)
	}

	identity, err := decodeBase58(dotParts[0])
	if err != nil {
		return nil, fmt.Errorf("decode identity key: %w", err)
	}
	encryption, err := decodeBase58(dotParts[1])
	if err != nil {
		return nil, fmt.Errorf("decode encryption key: %w", err)
	}
	gateway, err := decodeBase58(gatewayHalf)
	if err != nil {
		return nil, fmt.Errorf("decode gateway key: %w", err)
	}

	if len(identity) != pubkeySize || len(encryption) != pubkeySize || len(gateway) != pubkeySize {
		return nil, fmt.Errorf("nym address %q: expected 3x%d byte keys, got %d/%d/%d",
			addr, pubkeySize, len(identity), len(encryption), len(gateway))
	}

	out := make([]byte, 0, RecipientLen)
	out = append(out, identity...)
	out = append(out, encryption...)
	out = append(out, gateway...)
	return out, nil
}

// FormatRecipient is the inverse of ParseRecipient: it encodes 96 raw
// recipient bytes back into "identity.encryption@gateway" base58 form.
func FormatRecipient(b []byte) (string, error) {
	if len(b) != RecipientLen {
		return "", fmt.Errorf("expected %d recipient bytes, got %d", RecipientLen, len(b))
	}
	identity := encodeBase58(b[:pubkeySize])
	encryption := encodeBase58(b[pubkeySize : 2*pubkeySize])
	gateway := encodeBase58(b[2*pubkeySize:])
	return identity + "." + encryption + "@" + gateway, nil
}

// encodeBase58 is the inverse of decodeBase58 (plain base58, Bitcoin alphabet,
// no checksum).
func encodeBase58(data []byte) string {
	leadingZeros := 0
	for _, b := range data {
		if b != 0 {
			break
		}
		leadingZeros++
	}

	num := new(big.Int).SetBytes(data)
	base := big.NewInt(58)
	mod := new(big.Int)
	var out []byte
	for num.Sign() > 0 {
		num.DivMod(num, base, mod)
		out = append(out, base58Alphabet[mod.Int64()])
	}
	for i := 0; i < leadingZeros; i++ {
		out = append(out, '1')
	}
	// out was built least-significant-digit first; reverse it.
	for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 {
		out[i], out[j] = out[j], out[i]
	}
	return string(out)
}
