package utils

import (
	"os"
	"path/filepath"
	"testing"
)

func TestLoadMappingFile(t *testing.T) {
	t.Run("valid mapping", func(t *testing.T) {
		content := `{
			"GSK": {"species": "Roroa", "calltypes": {"Male": "Male - Solo"}},
			"Don't Know": {"species": "Don't Know"}
		}`
		path := createTempFile(t, content)
		defer os.Remove(path)

		mapping, err := LoadMappingFile(path)
		if err != nil {
			t.Fatalf("expected no error, got: %v", err)
		}
		if len(mapping) != 2 {
			t.Errorf("expected 2 entries, got %d", len(mapping))
		}
		if mapping["GSK"].Species != "Roroa" {
			t.Errorf("expected GSK -> Roroa, got %s", mapping["GSK"].Species)
		}
		if mapping["GSK"].Calltypes["Male"] != "Male - Solo" {
			t.Errorf("expected GSK Male -> Male - Solo, got %s", mapping["GSK"].Calltypes["Male"])
		}
	})

	t.Run("invalid JSON", func(t *testing.T) {
		content := `{invalid json}`
		path := createTempFile(t, content)
		defer os.Remove(path)

		_, err := LoadMappingFile(path)
		if err == nil {
			t.Fatal("expected error for invalid JSON")
		}
	})

	t.Run("empty file", func(t *testing.T) {
		content := `{}`
		path := createTempFile(t, content)
		defer os.Remove(path)

		_, err := LoadMappingFile(path)
		if err == nil {
			t.Fatal("expected error for empty mapping")
		}
	})

	t.Run("missing species field", func(t *testing.T) {
		content := `{"GSK": {"calltypes": {"Male": "Male - Solo"}}}`
		path := createTempFile(t, content)
		defer os.Remove(path)

		_, err := LoadMappingFile(path)
		if err == nil {
			t.Fatal("expected error for missing species field")
		}
	})

	t.Run("empty species field", func(t *testing.T) {
		content := `{"GSK": {"species": ""}}`
		path := createTempFile(t, content)
		defer os.Remove(path)

		_, err := LoadMappingFile(path)
		if err == nil {
			t.Fatal("expected error for empty species field")
		}
	})

	t.Run("nonexistent file", func(t *testing.T) {
		_, err := LoadMappingFile("/nonexistent/path/mapping.json")
		if err == nil {
			t.Fatal("expected error for nonexistent file")
		}
	})
}

func TestGetDBSpecies(t *testing.T) {
	mapping := MappingFile{
		"GSK": {Species: "Roroa"},
		"K-M": {Species: "Kiwi"},
	}

	t.Run("found", func(t *testing.T) {
		species, ok := mapping.GetDBSpecies("GSK")
		if !ok {
			t.Fatal("expected to find GSK")
		}
		if species != "Roroa" {
			t.Errorf("expected Roroa, got %s", species)
		}
	})

	t.Run("not found", func(t *testing.T) {
		_, ok := mapping.GetDBSpecies("UNKNOWN")
		if ok {
			t.Fatal("expected not to find UNKNOWN")
		}
	})
}

func TestGetDBCalltype(t *testing.T) {
	mapping := MappingFile{
		"GSK": {
			Species: "Roroa",
			Calltypes: map[string]string{
				"Male":   "Male - Solo",
				"Female": "Female - Solo",
			},
		},
		"K-M": {Species: "Kiwi"}, // no calltype mapping
	}

	t.Run("with mapping", func(t *testing.T) {
		ct := mapping.GetDBCalltype("GSK", "Male")
		if ct != "Male - Solo" {
			t.Errorf("expected 'Male - Solo', got %s", ct)
		}
	})

	t.Run("without mapping - passthrough", func(t *testing.T) {
		ct := mapping.GetDBCalltype("GSK", "Unknown")
		if ct != "Unknown" {
			t.Errorf("expected passthrough 'Unknown', got %s", ct)
		}
	})

	t.Run("species not in mapping - passthrough", func(t *testing.T) {
		ct := mapping.GetDBCalltype("UNKNOWN", "Male")
		if ct != "Male" {
			t.Errorf("expected passthrough 'Male', got %s", ct)
		}
	})

	t.Run("species without calltypes - passthrough", func(t *testing.T) {
		ct := mapping.GetDBCalltype("K-M", "Male")
		if ct != "Male" {
			t.Errorf("expected passthrough 'Male', got %s", ct)
		}
	})
}

func TestMappingValidationResult(t *testing.T) {
	t.Run("HasErrors - no errors", func(t *testing.T) {
		r := MappingValidationResult{}
		if r.HasErrors() {
			t.Error("expected no errors")
		}
	})

	t.Run("HasErrors - missing species", func(t *testing.T) {
		r := MappingValidationResult{MissingSpecies: []string{"UNKNOWN"}}
		if !r.HasErrors() {
			t.Error("expected errors")
		}
	})

	t.Run("HasErrors - missing DB species", func(t *testing.T) {
		r := MappingValidationResult{MissingDBSpecies: []string{"Phantom"}}
		if !r.HasErrors() {
			t.Error("expected errors")
		}
	})

	t.Run("HasErrors - missing calltypes", func(t *testing.T) {
		r := MappingValidationResult{MissingCalltypes: map[string]string{"GSK/Male": "Roroa/Male - Solo"}}
		if !r.HasErrors() {
			t.Error("expected errors")
		}
	})

	t.Run("Error - all error types", func(t *testing.T) {
		r := MappingValidationResult{
			MissingSpecies:   []string{"UNKNOWN"},
			MissingDBSpecies: []string{"Phantom"},
			MissingCalltypes: map[string]string{"GSK/Male": "Roroa/Male - Solo"},
		}
		errStr := r.Error()
		if errStr == "" {
			t.Error("expected non-empty error string")
		}
		// Check all parts are present
		if !containsSubstring(errStr, "UNKNOWN") {
			t.Error("error string should contain MISSING species")
		}
		if !containsSubstring(errStr, "Phantom") {
			t.Error("error string should contain missing DB species")
		}
		if !containsSubstring(errStr, "GSK/Male") {
			t.Error("error string should contain missing calltype")
		}
	})
}

// Helper functions

func createTempFile(t *testing.T, content string) string {
	t.Helper()
	tmpDir := t.TempDir()
	path := filepath.Join(tmpDir, "mapping.json")
	if err := os.WriteFile(path, []byte(content), 0644); err != nil {
		t.Fatalf("failed to create temp file: %v", err)
	}
	return path
}

func containsSubstring(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstringHelper(s, substr))
}

func containsSubstringHelper(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}

func TestMappingClassify(t *testing.T) {
	m := MappingFile{
		"noise":  {Species: MappingNegative},
		"ignore": {Species: MappingIgnore},
		"kiwi":   {Species: "Kiwi"},
	}

	c, k, ok := m.Classify("noise")
	if !ok || k != MappingNeg || c != "" {
		t.Error("failed classify negative")
	}

	c, k, ok = m.Classify("ignore")
	if !ok || k != MappingIgn || c != "" {
		t.Error("failed classify ignore")
	}

	c, k, ok = m.Classify("kiwi")
	if !ok || k != MappingReal || c != "Kiwi" {
		t.Error("failed classify real")
	}

	_, _, ok = m.Classify("missing")
	if ok {
		t.Error("expected missing to be not ok")
	}
}

func TestMappingValidateCoversSpecies(t *testing.T) {
	m := MappingFile{"kiwi": {Species: "Kiwi"}}

	missing := m.ValidateCoversSpecies(map[string]bool{"kiwi": true, "tui": true})
	if len(missing) != 1 || missing[0] != "tui" {
		t.Errorf("expected [tui], got %v", missing)
	}
}

func TestMappingClasses(t *testing.T) {
	m := MappingFile{
		"noise":     {Species: MappingNegative},
		"kiwi":      {Species: "Kiwi"},
		"tui":       {Species: "Tui"},
		"duplicate": {Species: "Kiwi"},
	}

	classes := m.Classes()
	if len(classes) != 2 || classes[0] != "Kiwi" || classes[1] != "Tui" {
		t.Errorf("expected [Kiwi, Tui], got %v", classes)
	}
}