package payments

import (
	"slices"
	"testing"
)

// allPresent is an account attached to every source (the maximal present set).
var allPresent = []Source{SourceDirect, SourceVK, SourceTelegram}

func TestSpendableSources(t *testing.T) {
	tests := []struct {
		name    string
		ctx     Context
		present []Source
		want    []Source
	}{
		{"vk android, vk present", Context{Kind: SourceVK, Subtype: "android"}, allPresent, []Source{SourceVK}},
		// VK-iOS spends its own vk segment: the freeze is purchase-only, spending is allowed.
		{"vk ios spends vk", Context{Kind: SourceVK, Subtype: SubtypeIOS}, allPresent, []Source{SourceVK}},
		{"vk android, vk absent", Context{Kind: SourceVK, Subtype: "android"}, []Source{SourceDirect}, nil},
		{"telegram", Context{Kind: SourceTelegram, Subtype: "web"}, allPresent, []Source{SourceTelegram}},
		{"telegram, tg absent", Context{Kind: SourceTelegram}, []Source{SourceVK}, nil},
		{"direct all present, priority", Context{Kind: SourceDirect, Subtype: "web"}, allPresent, []Source{SourceDirect, SourceVK, SourceTelegram}},
		{"direct, only vk+tg attached", Context{Kind: SourceDirect}, []Source{SourceTelegram, SourceVK}, []Source{SourceVK, SourceTelegram}},
		{"direct, nothing attached", Context{Kind: SourceDirect}, nil, nil},
		{"untrusted fail-closed", Context{}, allPresent, nil},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got := spendableSources(tc.ctx, tc.present); !slices.Equal(got, tc.want) {
				t.Errorf("spendableSources(%+v, %v) = %v, want %v", tc.ctx, tc.present, got, tc.want)
			}
		})
	}
}

func TestApplicableOrigins(t *testing.T) {
	tests := []struct {
		name    string
		ctx     Context
		present []Source
		want    []Source
	}{
		// A vk-origin benefit applies on VK-iOS (spending is allowed there — the freeze is purchase-only).
		{"vk ios applies", Context{Kind: SourceVK, Subtype: SubtypeIOS}, allPresent, []Source{SourceVK}},
		{"vk android", Context{Kind: SourceVK, Subtype: "android"}, allPresent, []Source{SourceVK}},
		{"telegram", Context{Kind: SourceTelegram}, allPresent, []Source{SourceTelegram}},
		{"direct all, priority", Context{Kind: SourceDirect}, allPresent, []Source{SourceDirect, SourceVK, SourceTelegram}},
		{"untrusted fail-closed", Context{}, allPresent, nil},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got := applicableOrigins(tc.ctx, tc.present); !slices.Equal(got, tc.want) {
				t.Errorf("applicableOrigins(%+v, %v) = %v, want %v", tc.ctx, tc.present, got, tc.want)
			}
		})
	}
}

// TestComplianceWall is the named unit-level compliance regression: a direct origin (externally
// paid, outside any store cash desk) must NEVER be applicable inside a VK or TG wrapper, and no
// segment is ever spendable there beyond the same-named one. The dangerous direction stays shut.
func TestComplianceWall(t *testing.T) {
	for _, kind := range []Source{SourceVK, SourceTelegram} {
		for _, sub := range []string{"android", "ios", "web"} {
			ctx := Context{Kind: kind, Subtype: sub}
			if slices.Contains(applicableOrigins(ctx, allPresent), SourceDirect) {
				t.Errorf("direct origin applies inside %s/%s — compliance wall breached", kind, sub)
			}
			for _, s := range spendableSources(ctx, allPresent) {
				if s != kind {
					t.Errorf("foreign segment %s spendable inside %s/%s", s, kind, sub)
				}
			}
			// The opposite store's origin must not leak in either (vk⊥tg).
			other := SourceVK
			if kind == SourceVK {
				other = SourceTelegram
			}
			if slices.Contains(applicableOrigins(ctx, allPresent), other) {
				t.Errorf("%s origin applies inside %s — cross-store leak", other, kind)
			}
		}
	}
}

func TestVisibleSources(t *testing.T) {
	if got := visibleSources(Context{Kind: SourceVK, Subtype: SubtypeIOS}); !slices.Equal(got, []Source{SourceVK}) {
		t.Errorf("VK visible = %v, want [vk] (direct/tg hidden in a store)", got)
	}
	if got := visibleSources(Context{Kind: SourceDirect}); !slices.Equal(got, allPresent) {
		t.Errorf("direct visible = %v, want all three", got)
	}
	if got := visibleSources(Context{}); !slices.Equal(got, allPresent) {
		t.Errorf("untrusted visible = %v, want all three (view-only)", got)
	}
}

func TestSourceForIdentityKind(t *testing.T) {
	tests := []struct {
		kind string
		want Source
		ok   bool
	}{
		{"vk", SourceVK, true},
		{"telegram", SourceTelegram, true},
		{"email", SourceDirect, true},
		{"robot", "", false},
		{"unknown", "", false},
	}
	for _, tc := range tests {
		got, ok := SourceForIdentityKind(tc.kind)
		if got != tc.want || ok != tc.ok {
			t.Errorf("SourceForIdentityKind(%q) = %q,%v want %q,%v", tc.kind, got, ok, tc.want, tc.ok)
		}
	}
}

func TestNewContextTrusted(t *testing.T) {
	if c := NewContext("vk", "ios"); !c.Trusted() || !c.vkFrozen() {
		t.Errorf("vk/ios: trusted=%v frozen=%v, want true/true", c.Trusted(), c.vkFrozen())
	}
	if c := NewContext("bogus", "web"); c.Trusted() {
		t.Errorf("bogus kind should be untrusted")
	}
	if c := NewContext("", ""); c.Trusted() {
		t.Errorf("empty kind should be untrusted")
	}
	if c := NewContext("direct", "web"); c.vkFrozen() {
		t.Errorf("direct is never frozen")
	}
}
