package payments

import (
	"strings"
	"testing"

	"github.com/google/uuid"
)

// TestProjectOfferPricing checks the happy path: a chip pack priced on every rail and a chip-priced
// value render into the two tables, pack table first, money formatted through Money.
func TestProjectOfferPricing(t *testing.T) {
	entries := []catalogEntry{
		{
			id:    uuid.New(),
			title: "50 «Фишек»",
			atoms: []atomQty{{atomType: atomChips, quantity: 50}},
			prices: []priceRow{
				{method: string(SourceDirect), currency: CurrencyRUB, amount: 20000},
				{method: string(SourceVK), currency: CurrencyVote, amount: 30},
				{method: string(SourceTelegram), currency: CurrencyStar, amount: 100},
			},
		},
		{
			id:     uuid.New(),
			title:  "200 подсказок",
			atoms:  []atomQty{{atomType: "hints", quantity: 200}},
			prices: []priceRow{{method: "", currency: CurrencyChip, amount: 50}},
		},
	}
	md := projectOfferPricing(entries)
	for _, want := range []string{
		"| Наименование | Рубли | Голоса в VK | Stars в Telegram |",
		"| --- | ---: | ---: | ---: |", // price columns right-aligned
		"| 50 «Фишек» | 200.00 | 30 | 100 |",
		"| Наименование | «Фишки» |",
		"| --- | ---: |",
		"| 200 подсказок | 50 |",
	} {
		if !strings.Contains(md, want) {
			t.Errorf("projection missing %q\n---\n%s", want, md)
		}
	}
	if strings.Index(md, "Приобретение") > strings.Index(md, "Использование") {
		t.Errorf("the pack table must precede the values table:\n%s", md)
	}
}

// TestProjectOfferPricingOrdering checks packs sort by ascending rouble price, and values sort by
// group (hints only → no-ads only → no-ads + hints) then ascending chip price within a group.
func TestProjectOfferPricingOrdering(t *testing.T) {
	pack := func(title string, rub int64) catalogEntry {
		return catalogEntry{
			id:     uuid.New(),
			title:  title,
			atoms:  []atomQty{{atomType: atomChips, quantity: 1}},
			prices: []priceRow{{method: string(SourceDirect), currency: CurrencyRUB, amount: rub}},
		}
	}
	value := func(title string, chips int64, atoms ...string) catalogEntry {
		e := catalogEntry{id: uuid.New(), title: title, prices: []priceRow{{method: "", currency: CurrencyChip, amount: chips}}}
		for _, a := range atoms {
			e.atoms = append(e.atoms, atomQty{atomType: a, quantity: 1})
		}
		return e
	}
	// Deliberately out of order on input.
	entries := []catalogEntry{
		pack("packDear", 30000),
		pack("packCheap", 10000),
		value("bundle", 500, "hints", "noads_days"),
		value("adsOnly", 150, "noads_days"),
		value("hintsBig", 200, "hints"),
		value("hintsSmall", 50, "hints"),
	}
	md := projectOfferPricing(entries)
	order := []string{"packCheap", "packDear", "hintsSmall", "hintsBig", "adsOnly", "bundle"}
	last := -1
	for _, title := range order {
		i := strings.Index(md, "| "+title+" |")
		if i < 0 {
			t.Fatalf("row %q missing:\n%s", title, md)
		}
		if i < last {
			t.Errorf("row %q out of order (want sequence %v):\n%s", title, order, md)
		}
		last = i
	}
}

// TestProjectOfferPricingMissingRailAndEscaping checks a pack missing a rail shows an em dash and a
// title carrying a pipe is escaped so the table layout survives.
func TestProjectOfferPricingMissingRailAndEscaping(t *testing.T) {
	entries := []catalogEntry{{
		id:    uuid.New(),
		title: "Bonus | pack",
		atoms: []atomQty{{atomType: atomChips, quantity: 10}},
		// A roubles price only — no VK, no Telegram.
		prices: []priceRow{{method: string(SourceDirect), currency: CurrencyRUB, amount: 9900}},
	}}
	md := projectOfferPricing(entries)
	if want := `| Bonus \| pack | 99.00 | — | — |`; !strings.Contains(md, want) {
		t.Errorf("want row %q in:\n%s", want, md)
	}
}

// TestProjectOfferPricingEscapesHTMLAndLinks checks an admin title carrying HTML or a markdown link
// is neutralised so it cannot inject markup into the public offer: the tag becomes entities and the
// link brackets are escaped (so no "javascript:" anchor forms). The raw metacharacters must not
// survive into the projected markdown.
func TestProjectOfferPricingEscapesHTMLAndLinks(t *testing.T) {
	entries := []catalogEntry{{
		id:     uuid.New(),
		title:  `<script>alert(1)</script> [x](javascript:alert(2)) & "q"`,
		atoms:  []atomQty{{atomType: "hints", quantity: 1}},
		prices: []priceRow{{method: "", currency: CurrencyChip, amount: 5}},
	}}
	md := projectOfferPricing(entries)
	for _, bad := range []string{"<script>", "</script>", "[x]", `& "q"`} {
		if strings.Contains(md, bad) {
			t.Errorf("unescaped %q survived into the projection:\n%s", bad, md)
		}
	}
	for _, want := range []string{"&lt;script&gt;", `\[x\]`, "&amp;", "&quot;q&quot;"} {
		if !strings.Contains(md, want) {
			t.Errorf("want escaped %q in:\n%s", want, md)
		}
	}
}

// TestProjectOfferPricingEmpty checks an empty catalog projects to the empty string (no stray table
// headers), so the offer's pricing marker is replaced with nothing.
func TestProjectOfferPricingEmpty(t *testing.T) {
	if got := projectOfferPricing(nil); got != "" {
		t.Errorf("empty catalog must project to empty string, got %q", got)
	}
}
