package bsv

import (
	"encoding/base64"
	"encoding/json"
	"os"
	"testing"
)

func TestMessageSignVerify(t *testing.T) {
	// from test/message/message.js
	wif := "cPBn5A4ikZvBTQ8D7NnvHZYCAxzDZ5Z2TSGW2LkyPiLxqYaJPBW4"
	address := "n1ZCYg9YXtB5XCZazLxSmPDa8iwJRZHhGx"
	priv, err := PrivateKeyFromWIF(wif)
	if err != nil {
		t.Fatal(err)
	}
	msg, err := NewMessage("hello, world")
	if err != nil {
		t.Fatal(err)
	}
	sig, err := msg.Sign(priv)
	if err != nil {
		t.Fatal(err)
	}
	if sig == "" {
		t.Fatal("empty signature")
	}
	ok, err := msg.Verify(address, sig)
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("signature should verify")
	}

	// known signature vector
	knownSig := "H/DIn8uA1scAuKLlCx+/9LnAcJtwQQ0PmcPrJUq90aboLv3fH5fFvY+vmbfOSFEtGarznYli6ShPr9RXwY9UrIY="
	ok2, err := msg.Verify(address, knownSig)
	if err != nil {
		t.Fatal(err)
	}
	if !ok2 {
		t.Fatal("known signature should verify")
	}
	// wrong address must fail
	badAddr := "mmRcrB5fTwgxaFJmVLNtaG8SV454y1E3kC"
	ok3, err := msg.Verify(badAddr, knownSig)
	if err != nil {
		t.Fatal(err)
	}
	if ok3 {
		t.Fatal("signature must not verify against wrong address")
	}
}

func TestMessageBufferData(t *testing.T) {
	wif := "cPBn5A4ikZvBTQ8D7NnvHZYCAxzDZ5Z2TSGW2LkyPiLxqYaJPBW4"
	address := "n1ZCYg9YXtB5XCZazLxSmPDa8iwJRZHhGx"
	priv, _ := PrivateKeyFromWIF(wif)
	bufData, _ := base64.StdEncoding.DecodeString("H/DIn8uA1scAuKLlCx+/9LnAcJtwQQ0PmcPrJUq90aboLv3fH5fFvY+vmbfOSFEtGarznYli6ShPr9RXwY9UrIY=")
	msg, _ := NewMessage(bufData)
	sig, err := msg.Sign(priv)
	if err != nil {
		t.Fatal(err)
	}
	ok, err := msg.Verify(address, sig)
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("buffer message signature should verify")
	}
}

// bip39Vector mirrors one row of test/mnemonic/data/fixtures.json.
type bip39Vector struct {
	English   string `json:"english"`
	Japanese  string `json:"japanese"`
	Chinese   string `json:"chinese"`
	Entropy   string `json:"entropy"`
	Seed      string `json:"seed"`
	Passphase string `json:"passphase"`
}

func TestBIP39Vectors(t *testing.T) {
	data, err := os.ReadFile("testdata/fixtures.json")
	if err != nil {
		t.Skip("mnemonic fixtures not available: " + err.Error())
	}
	var payload map[string][][]string
	if err := json.Unmarshal(data, &payload); err != nil {
		t.Fatal(err)
	}
	vectors := payload["english"]
	count := 0
	for i, v := range vectors {
		if len(v) < 4 {
			continue
		}
		passphrase := v[0]
		entropyHex := v[1]
		mnemonicStr := v[2]
		seedHex := v[3]

		t.Run("bip39_"+itoa(i), func(t *testing.T) {
			// validate mnemonic
			if !MnemonicIsValid(mnemonicStr, MnemonicWords["ENGLISH"]) {
				t.Fatalf("english mnemonic %q should be valid", mnemonicStr)
			}
			// entropy -> mnemonic
			entropy := fromHex(entropyHex)
			got, err := entropy2Mnemonic(entropy, MnemonicWords["ENGLISH"])
			if err != nil {
				t.Fatalf("entropy2mnemonic: %v", err)
			}
			if got != mnemonicStr {
				t.Fatalf("mnemonic = %q, want %q", got, mnemonicStr)
			}
			// mnemonic -> seed with the fixture passphrase
			m, err := MnemonicFromString(mnemonicStr, MnemonicWords["ENGLISH"])
			if err != nil {
				t.Fatalf("parse: %v", err)
			}
			seed := m.ToSeed(passphrase)
			if toHex(seed) != seedHex {
				t.Fatalf("seed = %s, want %s", toHex(seed), seedHex)
			}
		})
		count++
	}
	if count == 0 {
		t.Fatal("no mnemonic vectors processed")
	}
	t.Logf("passed %d BIP39 vectors", count)
}

func TestMnemonicBasics(t *testing.T) {
	// english wordlist complete
	if len(MnemonicWords["ENGLISH"]) != 2048 {
		t.Fatal("english wordlist length != 2048")
	}
	if MnemonicWords["ENGLISH"][0] != "abandon" {
		t.Fatal("english wordlist[0] != abandon")
	}
	if len(MnemonicWords["SPANISH"]) != 2048 {
		t.Fatal("spanish wordlist length != 2048")
	}
	if len(MnemonicWords["JAPANESE"]) != 2048 {
		t.Fatal("japanese wordlist length != 2048")
	}
	if len(MnemonicWords["CHINESE"]) != 2048 {
		t.Fatal("chinese wordlist length != 2048")
	}
	// random mnemonics differ
	m1, err := MnemonicFromRandom(nil)
	if err != nil {
		t.Fatal(err)
	}
	m2, err := MnemonicFromRandom(nil)
	if err != nil {
		t.Fatal(err)
	}
	if m1.ToString() == m2.ToString() {
		t.Fatal("two random mnemonics should differ")
	}
	if !MnemonicIsValid(m1.ToString(), MnemonicWords["ENGLISH"]) {
		t.Fatal("generated mnemonic should be valid")
	}
	// fixed phrase detection
	m3, err := MnemonicFromString("lab rescue lunch elbow recall phrase perfect donkey biology guess moment husband", nil)
	if err != nil {
		t.Fatal(err)
	}
	if !MnemonicIsValid(m3.ToString(), nil) {
		t.Fatal("known mnemonic should be valid")
	}
	// toHDPrivateKey works
	hd, err := m3.ToHDPrivateKey("", nil)
	if err != nil {
		t.Fatal(err)
	}
	if hd.Xprivkey == "" {
		t.Fatal("empty xprv")
	}
}

func TestMnemonicKnownInvalid(t *testing.T) {
	// "pilots foster ..." is not in the english wordlist -> unknown
	if MnemonicIsValid("pilots foster august tomorrow kit daughter unknown awesome model town village master", MnemonicWords["ENGLISH"]) {
		t.Fatal("should not be a valid english mnemonic")
	}
	// invalid checksum
	if MnemonicIsValid("monster foster august tomorrow kit daughter unknown awesome model town village pilot", MnemonicWords["ENGLISH"]) {
		t.Fatal("invalid checksum should fail")
	}
}

func TestECIESRoundtrip(t *testing.T) {
	alice, err := PrivateKeyFromRandom(nil)
	if err != nil {
		t.Fatal(err)
	}
	bob, err := PrivateKeyFromRandom(nil)
	if err != nil {
		t.Fatal(err)
	}
	message := "top secret message"

	// Alice encrypts to Bob
	enc := NewECIES(nil)
	enc.SetPrivateKey(alice)
	enc.SetPublicKey(bob.ToPublicKey())
	ciphertext, err := enc.Encrypt(message, nil)
	if err != nil {
		t.Fatal(err)
	}

	// Bob decrypts from Alice
	dec := NewECIES(nil)
	dec.SetPrivateKey(bob)
	plaintext, err := dec.Decrypt(ciphertext)
	if err != nil {
		t.Fatal(err)
	}
	if string(plaintext) != message {
		t.Fatalf("decrypted = %q, want %q", plaintext, message)
	}
}

func TestECIESNoKeyShortTag(t *testing.T) {
	priv, _ := PrivateKeyFromRandom(nil)
	pub := priv.ToPublicKey()
	msg := []byte("hello ecies")

	enc := NewECIES(map[string]interface{}{"noKey": true, "shortTag": true})
	enc.SetPrivateKey(priv)
	enc.SetPublicKey(pub)
	ciphertext, err := enc.Encrypt(msg, nil)
	if err != nil {
		t.Fatal(err)
	}

	dec := NewECIES(map[string]interface{}{"noKey": true, "shortTag": true})
	dec.SetPrivateKey(priv)
	dec.SetPublicKey(pub)
	plaintext, err := dec.Decrypt(ciphertext)
	if err != nil {
		t.Fatal(err)
	}
	if string(plaintext) != string(msg) {
		t.Fatalf("decrypted = %q, want %q", plaintext, msg)
	}
}

// TestECIESCrossCompatibility verifies Go output matches the JS library's
// bitcore-ecies output byte-for-byte for fixed keys and message.
func TestECIESCrossCompatibility(t *testing.T) {
	alice, err := PrivateKeyFromWIF("cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY")
	if err != nil {
		t.Fatal(err)
	}
	bob, err := PrivateKeyFromWIF("cQ3nCBQB9RsFSyjNQM15NQLVpXXMtWh9PUyeFz5KxLJCHsuRH2Su")
	if err != nil {
		t.Fatal(err)
	}
	message := "cross platform ecies test"

	// encrypt with Go (bitcore-ecies semantics: no BIE1 prefix)
	enc := NewECIES(nil)
	enc.SetPrivateKey(alice)
	enc.SetPublicKey(bob.ToPublicKey())
	ciphertext, err := enc.Encrypt(message, nil)
	if err != nil {
		t.Fatal(err)
	}

	// expected ciphertext generated by the JS library (bitcore-ecies)
	expected := "0223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5e0772f28aca12fba5f7dd33913b08377c12b3b355cad20da51166d64a7828bf3a9d78a31bf1a68ed57e8ceb3d10cad761bee0c5f2c47f5a032b5162200c0c9db7f88ef11ea4e44dd87657bf5bad45c572"
	if toHex(ciphertext) != expected {
		t.Fatalf("ciphertext mismatch:\n got %s\nwant %s", toHex(ciphertext), expected)
	}

	// decrypt with Go
	dec := NewECIES(nil)
	dec.SetPrivateKey(bob)
	plaintext, err := dec.Decrypt(ciphertext)
	if err != nil {
		t.Fatal(err)
	}
	if string(plaintext) != message {
		t.Fatalf("decrypted = %q, want %q", plaintext, message)
	}
}

// TestMessageCrossCompatibility verifies Go message signing matches JS.
func TestMessageCrossCompatibility(t *testing.T) {
	alice, err := PrivateKeyFromWIF("cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY")
	if err != nil {
		t.Fatal(err)
	}
	msg, err := NewMessage("cross platform ecies test")
	if err != nil {
		t.Fatal(err)
	}
	sig, err := msg.Sign(alice)
	if err != nil {
		t.Fatal(err)
	}
	// expected signature from the JS library
	expected := "IIPlC4SJCA+o1X1k2A1oZOCD/WaNe2uvemQ8numVHi6mPm6ii5srcZ/4oMJUY1sNAwHuVT7xVGO8iRlA3BLXXlw="
	if sig != expected {
		t.Fatalf("signature mismatch:\n got %s\nwant %s", sig, expected)
	}
	// verify roundtrip with the JS address
	ok, err := msg.Verify("1DUbYShrWmyhHd8qujZb7PxQ1sdLNWK98S", sig)
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("signature should verify")
	}
}

func TestBlockHeaderKnown(t *testing.T) {
	// genesis block header from bitcoind
	hex := "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"
	h, err := BlockHeaderFromString(hex)
	if err != nil {
		t.Fatal(err)
	}
	if h.Hash() != "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" {
		t.Fatalf("genesis hash = %s", h.Hash())
	}
	if h.ToString() != hex {
		t.Fatalf("header roundtrip mismatch")
	}
}

// TestMerkleBlockFromData tests merkleblock parsing/validation.
func TestMerkleBlockFromData(t *testing.T) {
	// header whose merkleRoot field (internal byte order) matches the single txid
	headerHex := "0100000000000000000000000000000000000000000000000000000000000000000000004a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b29ab5f49ffff001d1dac2b7c"
	header, err := BlockHeaderFromString(headerHex)
	if err != nil {
		t.Fatal(err)
	}
	txid := "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
	mb := &MerkleBlock{
		Header:          header,
		NumTransactions: 1,
		Hashes:          []string{txid},
		Flags:           []byte{0x03},
	}
	if !mb.ValidMerkleTree() {
		t.Fatal("single-tx merkleblock should be valid")
	}
	// roundtrip
	buf := mb.ToBuffer()
	mb2, err := MerkleBlockFromBuffer(buf)
	if err != nil {
		t.Fatal(err)
	}
	if mb2.Header.Hash() != header.Hash() || mb2.NumTransactions != 1 {
		t.Fatal("merkleblock roundtrip mismatch")
	}
}

// TestMerkleBlockBIP37 runs the official BIP37 merkleblock data (validity +
// filtered transaction extraction). The data is exported from the JS
// test/data/merkleblocks.js module into testdata/merkleblocks.json.
func TestMerkleBlockBIP37(t *testing.T) {
	data, err := os.ReadFile("testdata/merkleblocks.json")
	if err != nil {
		t.Skip("testdata/merkleblocks.json not available: " + err.Error())
	}
	var jsonVectors []map[string]interface{}
	if err := json.Unmarshal(data, &jsonVectors); err != nil {
		t.Fatalf("parse JSON: %v", err)
	}
	if len(jsonVectors) < 4 {
		t.Fatalf("expected at least 4 merkleblock vectors, got %d", len(jsonVectors))
	}

	// all vectors must be valid
	for i, j := range jsonVectors {
		t.Run("valid_"+itoa(i), func(t *testing.T) {
			mb, err := merkleBlockFromJSONObject(j)
			if err != nil {
				t.Fatalf("parse: %v", err)
			}
			if !mb.ValidMerkleTree() {
				t.Fatalf("merkleblock %d should be valid", i)
			}
		})
	}

	// vector 3 filters to a single known transaction (expected values from JS)
	t.Run("filtered", func(t *testing.T) {
		mb, err := merkleBlockFromJSONObject(jsonVectors[3])
		if err != nil {
			t.Fatal(err)
		}
		filtered, err := mb.FilteredTxsHash()
		if err != nil {
			t.Fatal(err)
		}
		want := "6f64fd5aa9dd01f74c03656d376625cf80328d83d9afebe60cc68b8f0e245bd9"
		if len(filtered) != 1 || filtered[0] != want {
			t.Fatalf("filtered = %v, want [%s]", filtered, want)
		}
		// hasTransaction with a matching txid
		has, err := mb.HasTransaction(want)
		if err != nil {
			t.Fatal(err)
		}
		if !has {
			t.Fatal("merkleblock should contain the filtered tx")
		}
		// a non-matching txid
		has2, err := mb.HasTransaction("0000000000000000000000000000000000000000000000000000000000000000")
		if err != nil {
			t.Fatal(err)
		}
		if has2 {
			t.Fatal("merkleblock should not contain a random txid")
		}
	})

	// corrupted merkleblocks must fail
	t.Run("too_many_hashes", func(t *testing.T) {
		mb, err := merkleBlockFromJSONObject(jsonVectors[0])
		if err != nil {
			t.Fatal(err)
		}
		mb.Hashes = []string{}
		for i := 0; i < 100; i++ {
			mb.Hashes = append(mb.Hashes, "0000000000000000000000000000000000000000000000000000000000000000")
		}
		if mb.ValidMerkleTree() {
			t.Fatal("too many hashes should be invalid")
		}
	})
	t.Run("too_few_flags", func(t *testing.T) {
		mb, err := merkleBlockFromJSONObject(jsonVectors[0])
		if err != nil {
			t.Fatal(err)
		}
		mb.Flags = []byte{0x00}
		if mb.ValidMerkleTree() {
			t.Fatal("too few flags should be invalid")
		}
	})
}

// merkleBlockFromJSONObject builds a MerkleBlock from the JS JSON object.
func merkleBlockFromJSONObject(obj map[string]interface{}) (*MerkleBlock, error) {
	headerObj, ok := obj["header"].(map[string]interface{})
	if !ok {
		return nil, newError("InvalidArgument", "missing header")
	}
	header, err := NewBlockHeader(headerObj)
	if err != nil {
		return nil, err
	}
	mb := &MerkleBlock{Header: header, Hashes: []string{}, Flags: []byte{}}
	if v, ok := obj["numTransactions"].(float64); ok {
		mb.NumTransactions = uint32(v)
	}
	if v, ok := obj["hashes"].([]interface{}); ok {
		for _, h := range v {
			if s, ok := h.(string); ok {
				mb.Hashes = append(mb.Hashes, s)
			}
		}
	}
	if v, ok := obj["flags"].([]interface{}); ok {
		for _, f := range v {
			if n, ok := f.(float64); ok {
				mb.Flags = append(mb.Flags, byte(n))
			}
		}
	}
	return mb, nil
}

func indexOf(s, sub string) int {
	for i := 0; i+len(sub) <= len(s); i++ {
		if s[i:i+len(sub)] == sub {
			return i
		}
	}
	return -1
}

// readFileBytes reads a file into a byte slice.

// TestBlockFromRealData parses the real testnet block data and compares
// against the expected JSON.
func TestBlockFromRealData(t *testing.T) {
	raw, err := readFileBytes("testdata/blk86756-testnet.dat")
	if err != nil {
		t.Skip("blk86756-testnet.dat not available: " + err.Error())
	}
	jsonData, err := readFileBytes("testdata/blk86756-testnet.json")
	if err != nil {
		t.Skip("blk86756-testnet.json not available")
	}
	var expected struct {
		Header struct {
			Hash string `json:"hash"`
		} `json:"header"`
		Transactions []struct {
			Hash string `json:"hash"`
		} `json:"transactions"`
	}
	if err := jsonUnmarshal(jsonData, &expected); err != nil {
		t.Fatal(err)
	}

	block, err := BlockFromRawBlock(raw)
	if err != nil {
		t.Fatal(err)
	}
	if block.Header.Hash() != expected.Header.Hash {
		t.Fatalf("block hash = %s, want %s", block.Header.Hash(), expected.Header.Hash)
	}
	if len(block.Transactions) != len(expected.Transactions) {
		t.Fatalf("tx count = %d, want %d", len(block.Transactions), len(expected.Transactions))
	}
	// compare the first tx hash
	if block.Transactions[0].GetID() != expected.Transactions[0].Hash {
		t.Fatalf("first txid = %s, want %s", block.Transactions[0].GetID(), expected.Transactions[0].Hash)
	}
	last := block.Transactions[len(block.Transactions)-1].GetID()
	if last != expected.Transactions[len(expected.Transactions)-1].Hash {
		t.Fatalf("last txid = %s, want %s", last, expected.Transactions[len(expected.Transactions)-1].Hash)
	}
	t.Logf("block %s with %d transactions parsed and verified", expected.Header.Hash, len(block.Transactions))
}

func readFileBytes(path string) ([]byte, error) {
	return os.ReadFile(path)
}