package utils

import (
	"os"
	"testing"
)

func TestDataFileParse(t *testing.T) {
	// Create a test .data file
	content := `[
		{"Operator": "Auto", "Reviewer": null, "Duration": 60.0},
		[10.0, 20.0, 0, 0, [{"species": "Kiwi", "certainty": 70, "filter": "test-filter"}]],
		[30.0, 40.0, 1000, 5000, [{"species": "Morepork", "certainty": 80, "filter": "M"}]]
	]`

	tmpfile, err := os.CreateTemp("", "test*.data")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(tmpfile.Name())

	if _, err := tmpfile.Write([]byte(content)); err != nil {
		t.Fatal(err)
	}
	tmpfile.Close()

	// Parse
	df, err := ParseDataFile(tmpfile.Name())
	if err != nil {
		t.Fatal(err)
	}

	// Check metadata
	if df.Meta.Operator != "Auto" {
		t.Errorf("expected Operator=Auto, got %s", df.Meta.Operator)
	}
	if df.Meta.Duration != 60.0 {
		t.Errorf("expected Duration=60.0, got %f", df.Meta.Duration)
	}

	// Check segments
	if len(df.Segments) != 2 {
		t.Errorf("expected 2 segments, got %d", len(df.Segments))
	}

	// Check first segment (sorted by start time)
	if df.Segments[0].StartTime != 10.0 {
		t.Errorf("expected StartTime=10.0, got %f", df.Segments[0].StartTime)
	}
	if df.Segments[0].EndTime != 20.0 {
		t.Errorf("expected EndTime=20.0, got %f", df.Segments[0].EndTime)
	}

	// Check labels
	if len(df.Segments[0].Labels) != 1 {
		t.Errorf("expected 1 label, got %d", len(df.Segments[0].Labels))
	}
	if df.Segments[0].Labels[0].Species != "Kiwi" {
		t.Errorf("expected Species=Kiwi, got %s", df.Segments[0].Labels[0].Species)
	}
	if df.Segments[0].Labels[0].Certainty != 70 {
		t.Errorf("expected Certainty=70, got %d", df.Segments[0].Labels[0].Certainty)
	}
}

func TestDataFileWrite(t *testing.T) {
	df := &DataFile{
		FilePath: "",
		Meta: &DataMeta{
			Operator: "Test",
			Reviewer: "David",
			Duration: 120.0,
		},
		Segments: []*Segment{
			{
				StartTime: 5.0,
				EndTime:   15.0,
				FreqLow:   0,
				FreqHigh:  0,
				Labels: []*Label{
					{Species: "Kiwi", Certainty: 100, Filter: "test"},
				},
			},
		},
	}

	tmpfile, err := os.CreateTemp("", "test*.data")
	if err != nil {
		t.Fatal(err)
	}
	tmpfile.Close()
	defer os.Remove(tmpfile.Name())

	// Write
	if err := df.Write(tmpfile.Name()); err != nil {
		t.Fatal(err)
	}

	// Re-parse and verify
	df2, err := ParseDataFile(tmpfile.Name())
	if err != nil {
		t.Fatal(err)
	}

	if df2.Meta.Reviewer != "David" {
		t.Errorf("expected Reviewer=David, got %s", df2.Meta.Reviewer)
	}
	if len(df2.Segments) != 1 {
		t.Errorf("expected 1 segment, got %d", len(df2.Segments))
	}
	if df2.Segments[0].Labels[0].Species != "Kiwi" {
		t.Errorf("expected Species=Kiwi, got %s", df2.Segments[0].Labels[0].Species)
	}
}

func TestHasFilterLabel(t *testing.T) {
	seg := &Segment{
		Labels: []*Label{
			{Species: "Kiwi", Filter: "test-filter"},
			{Species: "Morepork", Filter: "M"},
		},
	}

	if !seg.HasFilterLabel("test-filter") {
		t.Error("expected HasFilterLabel(test-filter)=true")
	}
	if !seg.HasFilterLabel("M") {
		t.Error("expected HasFilterLabel(M)=true")
	}
	if seg.HasFilterLabel("other") {
		t.Error("expected HasFilterLabel(other)=false")
	}
	if !seg.HasFilterLabel("") {
		t.Error("expected HasFilterLabel('')=true (no filter)")
	}
}

func TestGetFilterLabels(t *testing.T) {
	seg := &Segment{
		Labels: []*Label{
			{Species: "Kiwi", Filter: "test-filter", Certainty: 70},
			{Species: "Morepork", Filter: "M", Certainty: 80},
			{Species: "Don't Know", Filter: "test-filter", Certainty: 0},
		},
	}

	labels := seg.GetFilterLabels("test-filter")
	if len(labels) != 2 {
		t.Errorf("expected 2 labels, got %d", len(labels))
	}

	labels = seg.GetFilterLabels("")
	if len(labels) != 3 {
		t.Errorf("expected 3 labels (no filter), got %d", len(labels))
	}
}

func TestLabelComment(t *testing.T) {
	// Test parsing comment from .data file
	content := `[
		{"Operator": "Test", "Duration": 60.0},
		[10.0, 20.0, 0, 0, [{"species": "Kiwi", "certainty": 100, "filter": "M", "comment": "Good call"}]]
	]`

	tmpfile, err := os.CreateTemp("", "test*.data")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(tmpfile.Name())

	if _, err := tmpfile.Write([]byte(content)); err != nil {
		t.Fatal(err)
	}
	tmpfile.Close()

	df, err := ParseDataFile(tmpfile.Name())
	if err != nil {
		t.Fatal(err)
	}

	if df.Segments[0].Labels[0].Comment != "Good call" {
		t.Errorf("expected Comment='Good call', got '%s'", df.Segments[0].Labels[0].Comment)
	}

	// Test writing comment
	df.Segments[0].Labels[0].Comment = "Updated comment"

	tmpfile2, err := os.CreateTemp("", "test2*.data")
	if err != nil {
		t.Fatal(err)
	}
	tmpfile2.Close()
	defer os.Remove(tmpfile2.Name())

	if err := df.Write(tmpfile2.Name()); err != nil {
		t.Fatal(err)
	}

	// Re-parse and verify
	df2, err := ParseDataFile(tmpfile2.Name())
	if err != nil {
		t.Fatal(err)
	}

	if df2.Segments[0].Labels[0].Comment != "Updated comment" {
		t.Errorf("expected Comment='Updated comment', got '%s'", df2.Segments[0].Labels[0].Comment)
	}
}

func TestSkraakHashRoundTrip(t *testing.T) {
	// Test that skraak_hash in metadata is preserved through parse/write cycle
	df := &DataFile{
		Meta: &DataMeta{
			Operator: "Test",
			Duration: 60.0,
			Extra: map[string]any{
				"skraak_hash": "abc123def456",
			},
		},
		Segments: []*Segment{
			{
				StartTime: 10.0,
				EndTime:   20.0,
				Labels: []*Label{
					{Species: "Kiwi", Certainty: 100, Filter: "M"},
				},
			},
		},
	}

	tmpfile, err := os.CreateTemp("", "test*.data")
	if err != nil {
		t.Fatal(err)
	}
	tmpfile.Close()
	defer os.Remove(tmpfile.Name())

	// Write
	if err := df.Write(tmpfile.Name()); err != nil {
		t.Fatal(err)
	}

	// Re-parse
	df2, err := ParseDataFile(tmpfile.Name())
	if err != nil {
		t.Fatal(err)
	}

	// Verify skraak_hash preserved
	if df2.Meta.Extra == nil {
		t.Fatal("expected Extra to be non-nil")
	}
	hash, ok := df2.Meta.Extra["skraak_hash"].(string)
	if !ok {
		t.Fatal("expected skraak_hash to be string")
	}
	if hash != "abc123def456" {
		t.Errorf("expected skraak_hash=abc123def456, got %s", hash)
	}
}

func TestSkraakLabelIDRoundTrip(t *testing.T) {
	// Test that skraak_label_id in labels is preserved through parse/write cycle
	df := &DataFile{
		Meta: &DataMeta{
			Operator: "Test",
			Duration: 60.0,
		},
		Segments: []*Segment{
			{
				StartTime: 10.0,
				EndTime:   20.0,
				Labels: []*Label{
					{
						Species:   "Kiwi",
						Certainty: 100,
						Filter:    "M",
						Extra: map[string]any{
							"skraak_label_id": "label_abc123",
						},
					},
				},
			},
		},
	}

	tmpfile, err := os.CreateTemp("", "test*.data")
	if err != nil {
		t.Fatal(err)
	}
	tmpfile.Close()
	defer os.Remove(tmpfile.Name())

	// Write
	if err := df.Write(tmpfile.Name()); err != nil {
		t.Fatal(err)
	}

	// Re-parse
	df2, err := ParseDataFile(tmpfile.Name())
	if err != nil {
		t.Fatal(err)
	}

	// Verify skraak_label_id preserved
	if len(df2.Segments) != 1 {
		t.Fatalf("expected 1 segment, got %d", len(df2.Segments))
	}
	if len(df2.Segments[0].Labels) != 1 {
		t.Fatalf("expected 1 label, got %d", len(df2.Segments[0].Labels))
	}

	label := df2.Segments[0].Labels[0]
	if label.Extra == nil {
		t.Fatal("expected label Extra to be non-nil")
	}
	labelID, ok := label.Extra["skraak_label_id"].(string)
	if !ok {
		t.Fatal("expected skraak_label_id to be string")
	}
	if labelID != "label_abc123" {
		t.Errorf("expected skraak_label_id=label_abc123, got %s", labelID)
	}
}

func TestSkraakFieldsBothPresent(t *testing.T) {
	// Test both skraak_hash and skraak_label_id together
	df := &DataFile{
		Meta: &DataMeta{
			Operator: "Test",
			Duration: 60.0,
			Extra: map[string]any{
				"skraak_hash": "file_hash_xyz",
			},
		},
		Segments: []*Segment{
			{
				StartTime: 10.0,
				EndTime:   20.0,
				Labels: []*Label{
					{
						Species:   "Kiwi",
						Certainty: 100,
						Filter:    "M",
						Extra: map[string]any{
							"skraak_label_id": "label_id_1",
						},
					},
					{
						Species:   "Roroa",
						Certainty: 90,
						Filter:    "M",
						Extra: map[string]any{
							"skraak_label_id": "label_id_2",
						},
					},
				},
			},
		},
	}

	tmpfile, err := os.CreateTemp("", "test*.data")
	if err != nil {
		t.Fatal(err)
	}
	tmpfile.Close()
	defer os.Remove(tmpfile.Name())

	// Write
	if err := df.Write(tmpfile.Name()); err != nil {
		t.Fatal(err)
	}

	// Re-parse
	df2, err := ParseDataFile(tmpfile.Name())
	if err != nil {
		t.Fatal(err)
	}

	// Verify skraak_hash
	if df2.Meta.Extra["skraak_hash"] != "file_hash_xyz" {
		t.Errorf("expected skraak_hash=file_hash_xyz, got %v", df2.Meta.Extra["skraak_hash"])
	}

	// Verify both label IDs
	if len(df2.Segments[0].Labels) != 2 {
		t.Fatalf("expected 2 labels, got %d", len(df2.Segments[0].Labels))
	}

	labelIDs := []string{"label_id_1", "label_id_2"}
	for i, label := range df2.Segments[0].Labels {
		if label.Extra["skraak_label_id"] != labelIDs[i] {
			t.Errorf("label %d: expected skraak_label_id=%s, got %v", i, labelIDs[i], label.Extra["skraak_label_id"])
		}
	}
}

func TestSegmentMatchesFilters(t *testing.T) {
	// Create test segments with various labels
	seg := &Segment{
		Labels: []*Label{
			{Species: "Kiwi", Filter: "model-1.0", CallType: "Duet", Certainty: 70},
			{Species: "Morepork", Filter: "model-2.0", CallType: "", Certainty: 100},
		},
	}

	tests := []struct {
		name      string
		filter    string
		species   string
		callType  string
		certainty int
		want      bool
	}{
		{"no filters", "", "", "", -1, true},
		{"filter only match", "model-1.0", "", "", -1, true},
		{"filter only no match", "model-3.0", "", "", -1, false},
		{"species only match", "", "Kiwi", "", -1, true},
		{"species only no match", "", "Tomtit", "", -1, false},
		{"calltype only match", "", "", "Duet", -1, true},
		{"calltype only no match", "", "", "Male", -1, false},
		{"certainty match", "", "", "", 70, true},
		{"certainty no match", "", "", "", 80, false},
		{"certainty 100 match", "", "", "", 100, true},
		{"filter+species match", "model-1.0", "Kiwi", "", -1, true},
		{"filter+species+calltype match", "model-1.0", "Kiwi", "Duet", -1, true},
		{"filter+species+calltype+certainty match", "model-1.0", "Kiwi", "Duet", 70, true},
		{"filter+species+calltype certainty miss", "model-1.0", "Kiwi", "Duet", 100, false},
		{"filter match species miss", "model-1.0", "Morepork", "", -1, false},
		{"all miss", "model-3.0", "Tomtit", "Male", -1, false},
		{"CallTypeNone matches empty calltype", "model-2.0", "Morepork", CallTypeNone, -1, true},
		{"CallTypeNone skips non-empty calltype", "model-1.0", "Kiwi", CallTypeNone, -1, false},
		{"CallTypeNone + certainty match", "model-2.0", "Morepork", CallTypeNone, 100, true},
		{"CallTypeNone + certainty miss", "model-2.0", "Morepork", CallTypeNone, 70, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := seg.SegmentMatchesFilters(tt.filter, tt.species, tt.callType, tt.certainty)
			if got != tt.want {
				t.Errorf("SegmentMatchesFilters(%q, %q, %q, %d) = %v, want %v",
					tt.filter, tt.species, tt.callType, tt.certainty, got, tt.want)
			}
		})
	}
}

func TestParseSpeciesCallType(t *testing.T) {
	tests := []struct {
		input    string
		species  string
		callType string
	}{
		{"", "", ""},
		{"Kiwi", "Kiwi", ""},
		{"Kiwi+Duet", "Kiwi", "Duet"},
		{"GSK+Female", "GSK", "Female"},
		{"Species+With+Multiple+Plus", "Species", "With+Multiple+Plus"},
		{"Kiwi+_", "Kiwi", "_"},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			species, callType := ParseSpeciesCallType(tt.input)
			if species != tt.species || callType != tt.callType {
				t.Errorf("ParseSpeciesCallType(%q) = (%q, %q), want (%q, %q)",
					tt.input, species, callType, tt.species, tt.callType)
			}
		})
	}
}