package lnurl_test

import (
	"strings"
	"testing"

	"github.com/drjones/quantum-arcade/pkg/lnurl"
)

// The BIP-173 test vectors. An implementation that passes these produces
// strings other wallets will accept; one that does not produces codes that
// simply fail to scan, with no useful error for the player.
func TestBIP173ValidVectors(t *testing.T) {
	valid := []string{
		"A12UEL5L",
		"a12uel5l",
		"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
		"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
		// The 90-character vector, built rather than transcribed: getting the
		// run length wrong by hand produces a checksum failure that looks like
		// an implementation bug.
		"11" + strings.Repeat("q", 82) + "c8247j",
		"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
		"?1ezyfcl",
	}
	for _, v := range valid {
		if _, _, err := lnurl.Decode(v); err != nil {
			t.Errorf("valid vector %q rejected: %v", v, err)
		}
	}
}

func TestBIP173InvalidVectors(t *testing.T) {
	invalid := map[string]string{
		"A12UEL5X":      "bad checksum",
		"pzry9x0s0muk":  "no separator",
		"1pzry9x0s0muk": "empty hrp",
		"x1b4n0q5v":     "invalid character",
		"li1dgmt3":      "too short",
		"A1G7SGD8":      "bad checksum",
		"10a06t8":       "empty hrp",
		"1qzzfhee":      "empty hrp",
		"abc1rzg":       "too short",
		"in1muywd":      "bad checksum",
		"A12Uel5l":      "mixed case",
	}
	for v, why := range invalid {
		if _, _, err := lnurl.Decode(v); err == nil {
			t.Errorf("invalid vector %q (%s) was accepted", v, why)
		}
	}
}

func TestRoundTrip(t *testing.T) {
	cases := []string{
		"https://arcade.lan/lnurl/withdraw?k1=abc123",
		"http://10.0.0.5:8080/lnurl/withdraw?k1=" + strings.Repeat("f", 64),
		"https://example.com/",
	}
	for _, url := range cases {
		encoded, err := lnurl.EncodeURL(url)
		if err != nil {
			t.Fatalf("encoding %q: %v", url, err)
		}
		// Wallets receive these uppercase, so decoding must handle that.
		decoded, err := lnurl.DecodeURL(encoded)
		if err != nil {
			t.Fatalf("decoding %q: %v", encoded, err)
		}
		if decoded != url {
			t.Fatalf("round trip changed the URL: %q -> %q", url, decoded)
		}
	}
}

// LNURL strings are uppercase so the QR encodes in alphanumeric mode, which is
// substantially denser than byte mode and keeps the code scannable on a phone.
func TestEncodedLNURLIsUppercase(t *testing.T) {
	s, err := lnurl.EncodeURL("https://arcade.lan/lnurl/withdraw?k1=deadbeef")
	if err != nil {
		t.Fatal(err)
	}
	if s != strings.ToUpper(s) {
		t.Fatalf("LNURL is not uppercase: %q", s)
	}
	if !strings.HasPrefix(s, "LNURL1") {
		t.Fatalf("LNURL lacks the expected prefix: %q", s)
	}
}

// A tampered character must fail the checksum rather than decode to a
// different URL — otherwise a corrupted scan could point a wallet somewhere
// unintended.
func TestTamperingIsDetected(t *testing.T) {
	original := "https://arcade.lan/lnurl/withdraw?k1=abc123"
	encoded, err := lnurl.EncodeURL(original)
	if err != nil {
		t.Fatal(err)
	}

	detected := 0
	attempts := 0
	for i := 6; i < len(encoded); i++ {
		for _, sub := range "QPZRY9X8" {
			if rune(encoded[i]) == sub {
				continue
			}
			attempts++
			tampered := encoded[:i] + string(sub) + encoded[i+1:]
			if _, err := lnurl.DecodeURL(tampered); err != nil {
				detected++
			}
		}
	}
	if attempts == 0 {
		t.Fatal("no tampering attempts were made")
	}
	// The checksum catches all single-character substitutions by design.
	if detected != attempts {
		t.Fatalf("only %d of %d single-character changes were detected", detected, attempts)
	}
}

func TestWrongPrefixRejected(t *testing.T) {
	// A valid bech32 string that is not an LNURL must not be accepted as one.
	other, err := lnurl.Encode("lnbc", []byte("not an lnurl"))
	if err != nil {
		t.Fatal(err)
	}
	if _, err := lnurl.DecodeURL(other); err == nil {
		t.Fatal("a non-LNURL bech32 string was accepted as an LNURL")
	}
}
