package tools

import (
	"math/rand"
	"testing"

	"skraak/utils"
)

func TestTotalSegmentsRespectsFilters(t *testing.T) {
	// Create test data files with different species and filters
	df1 := &utils.DataFile{
		FilePath: "/test/file1.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0"},
				},
			},
			{
				StartTime: 10,
				EndTime:   20,
				Labels: []*utils.Label{
					{Species: "Tomtit", Filter: "model-1.0"},
				},
			},
		},
	}

	df2 := &utils.DataFile{
		FilePath: "/test/file2.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0"},
				},
			},
		},
	}

	// Test 1: No filters - should count all segments (3)
	state1 := NewClassifyState(ClassifyConfig{Certainty: -1}, []*utils.DataFile{df1, df2})
	if got := state1.TotalSegments(); got != 3 {
		t.Errorf("No filters: expected 3 segments, got %d", got)
	}

	// Test 2: Filter by species "Kiwi" - should count only Kiwi segments (2)
	state2 := NewClassifyState(ClassifyConfig{Species: "Kiwi", Certainty: -1}, []*utils.DataFile{df1, df2})
	if got := state2.TotalSegments(); got != 2 {
		t.Errorf("Species=Kiwi: expected 2 segments, got %d", got)
	}

	// Test 3: Filter by species "Tomtit" - should count only Tomtit segments (1)
	state3 := NewClassifyState(ClassifyConfig{Species: "Tomtit", Certainty: -1}, []*utils.DataFile{df1, df2})
	if got := state3.TotalSegments(); got != 1 {
		t.Errorf("Species=Tomtit: expected 1 segment, got %d", got)
	}

	// Test 4: Filter by filter name "model-1.0" - should count all segments (3)
	state4 := NewClassifyState(ClassifyConfig{Filter: "model-1.0", Certainty: -1}, []*utils.DataFile{df1, df2})
	if got := state4.TotalSegments(); got != 3 {
		t.Errorf("Filter=model-1.0: expected 3 segments, got %d", got)
	}

	// Test 5: Filter by non-existent species - should count 0
	state5 := NewClassifyState(ClassifyConfig{Species: "NonExistent", Certainty: -1}, []*utils.DataFile{df1, df2})
	if got := state5.TotalSegments(); got != 0 {
		t.Errorf("Species=NonExistent: expected 0 segments, got %d", got)
	}

	// Test 6: Combined filter + species
	df3 := &utils.DataFile{
		FilePath: "/test/file3.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0", CallType: "Duet"},
				},
			},
			{
				StartTime: 10,
				EndTime:   20,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-2.0", CallType: "Male"},
				},
			},
		},
	}
	state6 := NewClassifyState(ClassifyConfig{Filter: "model-1.0", Species: "Kiwi", Certainty: -1}, []*utils.DataFile{df3})
	if got := state6.TotalSegments(); got != 1 {
		t.Errorf("Filter=model-1.0 + Species=Kiwi: expected 1 segment, got %d", got)
	}
}

func TestCurrentSegmentNumberWithFilters(t *testing.T) {
	// Create test data files
	df1 := &utils.DataFile{
		FilePath: "/test/file1.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0"},
				},
			},
			{
				StartTime: 10,
				EndTime:   20,
				Labels: []*utils.Label{
					{Species: "Tomtit", Filter: "model-1.0"},
				},
			},
		},
	}

	df2 := &utils.DataFile{
		FilePath: "/test/file2.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0"},
				},
			},
		},
	}

	// Test: Filter by species "Kiwi", at file 2, segment 0
	// Should report current segment as 2 (first Kiwi in df1 + first Kiwi in df2)
	state := NewClassifyState(ClassifyConfig{Species: "Kiwi", Certainty: -1}, []*utils.DataFile{df1, df2})
	state.FileIdx = 1 // at df2
	state.SegmentIdx = 0

	if got := state.CurrentSegmentNumber(); got != 2 {
		t.Errorf("Species=Kiwi, at file 2, seg 0: expected current segment 2, got %d", got)
	}
}

func TestCertaintyFiltering(t *testing.T) {
	// Create test data files with different certainty levels
	df := &utils.DataFile{
		FilePath: "/test/file1.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0", Certainty: 70},
				},
			},
			{
				StartTime: 10,
				EndTime:   20,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0", Certainty: 100},
				},
			},
			{
				StartTime: 20,
				EndTime:   30,
				Labels: []*utils.Label{
					{Species: "Tomtit", Filter: "model-1.0", Certainty: 70},
				},
			},
		},
	}

	// Test 1: Filter by certainty 70 - should get 2 segments
	state1 := NewClassifyState(ClassifyConfig{Certainty: 70}, []*utils.DataFile{df})
	if got := state1.TotalSegments(); got != 2 {
		t.Errorf("Certainty=70: expected 2 segments, got %d", got)
	}

	// Test 2: Filter by certainty 100 - should get 1 segment
	state2 := NewClassifyState(ClassifyConfig{Certainty: 100}, []*utils.DataFile{df})
	if got := state2.TotalSegments(); got != 1 {
		t.Errorf("Certainty=100: expected 1 segment, got %d", got)
	}

	// Test 3: Filter by certainty 0 - should get 0 segments
	state3 := NewClassifyState(ClassifyConfig{Certainty: 0}, []*utils.DataFile{df})
	if got := state3.TotalSegments(); got != 0 {
		t.Errorf("Certainty=0: expected 0 segments, got %d", got)
	}

	// Test 4: Combined species + certainty
	state4 := NewClassifyState(ClassifyConfig{Species: "Kiwi", Certainty: 70}, []*utils.DataFile{df})
	if got := state4.TotalSegments(); got != 1 {
		t.Errorf("Species=Kiwi + Certainty=70: expected 1 segment, got %d", got)
	}
}

func TestSampling(t *testing.T) {
	makeSegs := func(n int) []*utils.Segment {
		s := make([]*utils.Segment, n)
		for i := range s {
			s[i] = &utils.Segment{StartTime: float64(i), EndTime: float64(i + 1)}
		}
		return s
	}

	df1 := &utils.DataFile{FilePath: "/test/f1.data", Segments: makeSegs(6)}
	df2 := &utils.DataFile{FilePath: "/test/f2.data", Segments: makeSegs(4)}
	kept := []*utils.DataFile{df1, df2}
	cached := [][]*utils.Segment{df1.Segments, df2.Segments}

	countTotal := func(c [][]*utils.Segment) int {
		n := 0
		for _, s := range c {
			n += len(s)
		}
		return n
	}

	// 50% of 10 → 5
	k, c := applySampling(kept, cached, 50, rand.New(rand.NewSource(42)))
	if got := countTotal(c); got != 5 {
		t.Errorf("sample 50%%: expected 5, got %d", got)
	}
	// Files must be in original chronological order
	for i := 1; i < len(k); i++ {
		if k[i].FilePath < k[i-1].FilePath {
			t.Errorf("sample 50%%: files out of order at index %d", i)
		}
	}

	// 10% of 10 → 1
	_, c2 := applySampling(kept, cached, 10, rand.New(rand.NewSource(42)))
	if got := countTotal(c2); got != 1 {
		t.Errorf("sample 10%%: expected 1, got %d", got)
	}

	// 1% of 10 → clamp to 1
	_, c3 := applySampling(kept, cached, 1, rand.New(rand.NewSource(42)))
	if got := countTotal(c3); got != 1 {
		t.Errorf("sample 1%%: expected 1 (clamped), got %d", got)
	}

	// 99% of 10 → 9
	_, c4 := applySampling(kept, cached, 99, rand.New(rand.NewSource(42)))
	if got := countTotal(c4); got != 9 {
		t.Errorf("sample 99%%: expected 9, got %d", got)
	}
}

func TestCertaintyPruning(t *testing.T) {
	// Simulate the bug: first file has no matching certainty segments
	df1 := &utils.DataFile{
		FilePath: "/test/file1.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0", Certainty: 70},
				},
			},
		},
	}

	df2 := &utils.DataFile{
		FilePath: "/test/file2.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0", Certainty: 100},
				},
			},
		},
	}

	// Without pruning (old bug): file1 is first, has no certainty=100 segments
	// CurrentSegment() would return nil even though TotalSegments() > 0
	state := NewClassifyState(ClassifyConfig{Certainty: 100}, []*utils.DataFile{df1, df2})

	// TotalSegments should be 1 (only file2 has certainty 100)
	if got := state.TotalSegments(); got != 1 {
		t.Errorf("Certainty=100: expected 1 segment, got %d", got)
	}

	// CurrentSegment should work if files are properly pruned
	// Note: this test assumes LoadDataFiles does the pruning
	// Here we test the state after manual construction
}

func TestCallTypeNoneFiltering(t *testing.T) {
	// Create test data: Kiwi with calltype, Kiwi without, Tomtit without
	df := &utils.DataFile{
		FilePath: "/test/file1.data",
		Segments: []*utils.Segment{
			{
				StartTime: 0,
				EndTime:   10,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0", CallType: "Male"},
				},
			},
			{
				StartTime: 10,
				EndTime:   20,
				Labels: []*utils.Label{
					{Species: "Kiwi", Filter: "model-1.0"}, // no calltype
				},
			},
			{
				StartTime: 20,
				EndTime:   30,
				Labels: []*utils.Label{
					{Species: "Tomtit", Filter: "model-1.0"}, // no calltype, wrong species
				},
			},
		},
	}

	// Test 1: --species Kiwi+_ should match only Kiwi with no calltype (1 segment)
	state1 := NewClassifyState(ClassifyConfig{Species: "Kiwi", CallType: utils.CallTypeNone, Certainty: -1}, []*utils.DataFile{df})
	if got := state1.TotalSegments(); got != 1 {
		t.Errorf("Species=Kiwi+_: expected 1 segment, got %d", got)
	}

	// Test 2: --species Kiwi should still match all Kiwi (2 segments)
	state2 := NewClassifyState(ClassifyConfig{Species: "Kiwi", Certainty: -1}, []*utils.DataFile{df})
	if got := state2.TotalSegments(); got != 2 {
		t.Errorf("Species=Kiwi: expected 2 segments, got %d", got)
	}

	// Test 3: --species Kiwi+Male should still work as before (1 segment)
	state3 := NewClassifyState(ClassifyConfig{Species: "Kiwi", CallType: "Male", Certainty: -1}, []*utils.DataFile{df})
	if got := state3.TotalSegments(); got != 1 {
		t.Errorf("Species=Kiwi+Male: expected 1 segment, got %d", got)
	}
}