package ante

import (
	"math/big"
	"testing"
	"time"
)

// ==============================
// OFAC Tests
// ==============================

func TestOFAC_BlocksSanctionedSender(t *testing.T) {
	h := NewComplianceAnteHandler()
	h.AddToOFACList("0xSanctioned")

	result := h.AnteHandle("0xSanctioned", "0xNormal", big.NewInt(1000))
	if result.Allowed {
		t.Fatal("should block sanctioned sender")
	}
	if result.Reason == "" {
		t.Fatal("should provide reason")
	}
}

func TestOFAC_BlocksSanctionedReceiver(t *testing.T) {
	h := NewComplianceAnteHandler()
	h.AddToOFACList("0xSanctioned")

	result := h.AnteHandle("0xNormal", "0xSanctioned", big.NewInt(1000))
	if result.Allowed {
		t.Fatal("should block sanctioned receiver")
	}
}

func TestOFAC_AllowsCleanAddresses(t *testing.T) {
	h := NewComplianceAnteHandler()
	h.AddToOFACList("0xSanctioned")

	result := h.AnteHandle("0xAlice", "0xBob", big.NewInt(1000))
	if !result.Allowed {
		t.Fatalf("should allow clean addresses, got reason: %s", result.Reason)
	}
}

func TestOFAC_BulkUpdate(t *testing.T) {
	h := NewComplianceAnteHandler()

	addresses := []string{"0xA", "0xB", "0xC"}
	h.UpdateOFACList(addresses)

	if h.GetOFACListSize() != 3 {
		t.Fatalf("expected 3, got %d", h.GetOFACListSize())
	}

	for _, addr := range addresses {
		if !h.IsOFACSanctioned(addr) {
			t.Fatalf("%s should be sanctioned", addr)
		}
	}

	if h.IsOFACSanctioned("0xClean") {
		t.Fatal("0xClean should not be sanctioned")
	}
}

func TestOFAC_RemoveFromList(t *testing.T) {
	h := NewComplianceAnteHandler()
	h.AddToOFACList("0xAddr")
	if !h.IsOFACSanctioned("0xAddr") {
		t.Fatal("should be sanctioned")
	}

	h.RemoveFromOFACList("0xAddr")
	if h.IsOFACSanctioned("0xAddr") {
		t.Fatal("should no longer be sanctioned")
	}
}

func TestOFAC_UpdateReplacesOldList(t *testing.T) {
	h := NewComplianceAnteHandler()
	h.AddToOFACList("0xOld")

	h.UpdateOFACList([]string{"0xNew"})

	if h.IsOFACSanctioned("0xOld") {
		t.Fatal("0xOld should be removed after full update")
	}
	if !h.IsOFACSanctioned("0xNew") {
		t.Fatal("0xNew should be in the new list")
	}
}

// ==============================
// Travel Rule Tests
// ==============================

func TestTravelRule_BlocksLargeTransferWithoutRecord(t *testing.T) {
	h := NewComplianceAnteHandler()

	// $3,000 USDC = 3000 * 1e6 = 3,000,000,000
	amount := new(big.Int).Mul(big.NewInt(3000), big.NewInt(1e6))

	result := h.AnteHandle("0xAlice", "0xBob", amount)
	if result.Allowed {
		t.Fatal("should block large transfer without Travel Rule record")
	}
}

func TestTravelRule_AllowsLargeTransferWithRecord(t *testing.T) {
	h := NewComplianceAnteHandler()
	h.RecordTravelRule("0xAlice", "0xBob")

	amount := new(big.Int).Mul(big.NewInt(5000), big.NewInt(1e6))

	result := h.AnteHandle("0xAlice", "0xBob", amount)
	if !result.Allowed {
		t.Fatalf("should allow large transfer with Travel Rule record, reason: %s", result.Reason)
	}
}

func TestTravelRule_AllowsSmallTransferWithoutRecord(t *testing.T) {
	h := NewComplianceAnteHandler()

	// $2,999 — below threshold
	amount := new(big.Int).Mul(big.NewInt(2999), big.NewInt(1e6))

	result := h.AnteHandle("0xAlice", "0xBob", amount)
	if !result.Allowed {
		t.Fatal("should allow transfer below $3,000 without Travel Rule record")
	}
}

func TestTravelRule_ExactThreshold(t *testing.T) {
	h := NewComplianceAnteHandler()

	// Exactly $3,000 — at threshold, should require record
	amount := new(big.Int).Mul(big.NewInt(3000), big.NewInt(1e6))

	result := h.AnteHandle("0xAlice", "0xBob", amount)
	if result.Allowed {
		t.Fatal("$3,000 exactly should require Travel Rule record")
	}
}

func TestTravelRule_HasRecord(t *testing.T) {
	h := NewComplianceAnteHandler()

	if h.HasTravelRuleRecord("0xA", "0xB") {
		t.Fatal("should not have record initially")
	}

	h.RecordTravelRule("0xA", "0xB")

	if !h.HasTravelRuleRecord("0xA", "0xB") {
		t.Fatal("should have record after recording")
	}

	// Direction matters
	if h.HasTravelRuleRecord("0xB", "0xA") {
		t.Fatal("reverse direction should not have record")
	}
}

// ==============================
// Structuring Detection Tests
// ==============================

func TestStructuring_NoFlagForSingleSmallTransfer(t *testing.T) {
	h := NewComplianceAnteHandler()

	amount := new(big.Int).Mul(big.NewInt(500), big.NewInt(1e6)) // $500
	result := h.AnteHandle("0xAlice", "0xBob", amount)

	if result.Suspicious {
		t.Fatal("single small transfer should not be flagged")
	}
}

func TestStructuring_FlagsMultipleSmallTransfersExceedingThreshold(t *testing.T) {
	h := NewComplianceAnteHandler()

	// Record several small transfers totaling > $3,000
	for i := 0; i < 5; i++ {
		h.RecordTransfer("0xAlice", new(big.Int).Mul(big.NewInt(500), big.NewInt(1e6))) // $500 each
	}

	// 6th transfer: cumulative = $3,000
	amount := new(big.Int).Mul(big.NewInt(500), big.NewInt(1e6))
	result := h.AnteHandle("0xAlice", "0xBob", amount)

	if !result.Suspicious {
		t.Fatal("cumulative small transfers exceeding $3,000 should be flagged as suspicious")
	}
	// But still allowed (not blocked)
	if !result.Allowed {
		t.Fatal("structuring should flag but not block")
	}
}

func TestStructuring_DoesNotFlagIfSingleLargeTransfer(t *testing.T) {
	h := NewComplianceAnteHandler()

	// Record one large transfer > threshold
	h.RecordTransfer("0xAlice", new(big.Int).Mul(big.NewInt(4000), big.NewInt(1e6)))

	// Another small transfer
	amount := new(big.Int).Mul(big.NewInt(100), big.NewInt(1e6))
	result := h.AnteHandle("0xAlice", "0xBob", amount)

	// Not structuring because previous transfers include one >= threshold
	if result.Suspicious {
		t.Fatal("should not flag as structuring if previous transfers include large ones")
	}
}

func TestStructuring_SlidingWindowExpiry(t *testing.T) {
	h := NewComplianceAnteHandler()
	// Override window to 1ms for testing
	h.structuringWindow = 1 * time.Millisecond

	h.RecordTransfer("0xAlice", new(big.Int).Mul(big.NewInt(2000), big.NewInt(1e6)))

	// Wait for window to expire
	time.Sleep(5 * time.Millisecond)

	amount := new(big.Int).Mul(big.NewInt(1500), big.NewInt(1e6))
	result := h.AnteHandle("0xAlice", "0xBob", amount)

	if result.Suspicious {
		t.Fatal("expired window records should not contribute to structuring detection")
	}
}

// ==============================
// Combined Scenario Tests
// ==============================

func TestCombined_OFACTakesPrecedenceOverTravelRule(t *testing.T) {
	h := NewComplianceAnteHandler()
	h.AddToOFACList("0xBad")
	h.RecordTravelRule("0xBad", "0xGood")

	amount := new(big.Int).Mul(big.NewInt(5000), big.NewInt(1e6))
	result := h.AnteHandle("0xBad", "0xGood", amount)

	if result.Allowed {
		t.Fatal("OFAC should block even if Travel Rule record exists")
	}
}

func TestCombined_StructuringPlusTravelRule(t *testing.T) {
	h := NewComplianceAnteHandler()

	// Record small transfers approaching threshold
	for i := 0; i < 3; i++ {
		h.RecordTransfer("0xAlice", new(big.Int).Mul(big.NewInt(900), big.NewInt(1e6)))
	}

	// Small transfer that pushes cumulative over threshold
	// But this single transfer is below threshold, so no Travel Rule needed
	amount := new(big.Int).Mul(big.NewInt(500), big.NewInt(1e6))
	result := h.AnteHandle("0xAlice", "0xBob", amount)

	if !result.Allowed {
		t.Fatal("below-threshold transfer should still be allowed")
	}
	if !result.Suspicious {
		t.Fatal("should be flagged as suspicious structuring")
	}
}

func TestConcurrency_SafeAccess(t *testing.T) {
	h := NewComplianceAnteHandler()

	done := make(chan struct{})

	// Writer goroutine
	go func() {
		for i := 0; i < 100; i++ {
			h.AddToOFACList("0xAddr")
			h.RemoveFromOFACList("0xAddr")
			h.RecordTravelRule("0xA", "0xB")
			h.RecordTransfer("0xC", big.NewInt(1000))
		}
		done <- struct{}{}
	}()

	// Reader goroutine
	go func() {
		for i := 0; i < 100; i++ {
			h.AnteHandle("0xAlice", "0xBob", big.NewInt(1000))
			h.IsOFACSanctioned("0xAddr")
			h.HasTravelRuleRecord("0xA", "0xB")
			h.GetOFACListSize()
		}
		done <- struct{}{}
	}()

	<-done
	<-done
}
