package quality

import (
	"context"
	"testing"
)

// DetectorMock implements the Detector interface for testing
type DetectorMock struct {
	name     string
	severity Severity
}

func (m *DetectorMock) Name() string {
	return m.name
}

func (m *DetectorMock) Detect(ctx context.Context, path string, config *Config) ([]Finding, error) {
	return []Finding{
		{
			Type:     "mock_finding",
			Severity: m.severity,
			Status:   StatusOpen,
			Score:    5,
		},
	}, nil
}

func (m *DetectorMock) Severity() Severity {
	return m.severity
}

// LanguageDetectorMock implements both Detector and LanguageDetector interfaces
type LanguageDetectorMock struct {
	*DetectorMock
	supportedLanguages []string
}

func (m *LanguageDetectorMock) SupportedLanguages() []string {
	return m.supportedLanguages
}

func (m *LanguageDetectorMock) ExtractFunctions(ctx context.Context, files []string) ([]FunctionInfo, error) {
	var functions []FunctionInfo
	for _, file := range files {
		functions = append(functions, FunctionInfo{
			Name: "test_func",
			File: file,
		})
	}
	return functions, nil
}

func (m *LanguageDetectorMock) ExtractClasses(ctx context.Context, files []string) ([]ClassInfo, error) {
	var classes []ClassInfo
	for _, file := range files {
		classes = append(classes, ClassInfo{
			Name: "TestClass",
			File: file,
		})
	}
	return classes, nil
}

// FileFinderMock implements the FileFinder interface for testing
type FileFinderMock struct {
	files []string
}

func (m *FileFinderMock) FindFiles(path string, language string) ([]string, error) {
	return m.files, nil
}

func (m *FileFinderMock) IsSourceFile(path string, language string) bool {
	return true
}

func TestNewBaseDetector(t *testing.T) {
	finder := &FileFinderMock{files: []string{"test.go"}}
	detector := NewBaseDetector("test-detector", SeverityT2, finder)

	if detector == nil {
		t.Error("NewBaseDetector() should not return nil")
	}

	if detector.name != "test-detector" {
		t.Errorf("NewBaseDetector() name = %v, want test-detector", detector.name)
	}

	if detector.severity != SeverityT2 {
		t.Errorf("NewBaseDetector() severity = %v, want T2", detector.severity)
	}

	if detector.finder != finder {
		t.Error("NewBaseDetector() finder not set correctly")
	}
}

func TestBaseDetector_Name(t *testing.T) {
	detector := NewBaseDetector("test-name", SeverityT1, nil)

	if detector.Name() != "test-name" {
		t.Errorf("Name() = %v, want test-name", detector.Name())
	}
}

func TestBaseDetector_Severity(t *testing.T) {
	tests := []struct {
		name     string
		severity Severity
	}{
		{"T1 severity", SeverityT1},
		{"T2 severity", SeverityT2},
		{"T3 severity", SeverityT3},
		{"T4 severity", SeverityT4},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			detector := NewBaseDetector("test", tt.severity, nil)
			if detector.Severity() != tt.severity {
				t.Errorf("Severity() = %v, want %v", detector.Severity(), tt.severity)
			}
		})
	}
}

func TestBaseDetector_FindFiles(t *testing.T) {
	tests := []struct {
		name     string
		finder   FileFinder
		expected []string
	}{
		{
			name:     "with finder",
			finder:   &FileFinderMock{files: []string{"file1.go", "file2.go"}},
			expected: []string{"file1.go", "file2.go"},
		},
		{
			name:     "without finder",
			finder:   nil,
			expected: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			detector := NewBaseDetector("test", SeverityT1, tt.finder)
			files, err := detector.FindFiles("/test/path", "go")

			if err != nil {
				t.Errorf("FindFiles() unexpected error: %v", err)
			}

			if len(files) != len(tt.expected) {
				t.Errorf("FindFiles() expected %d files, got %d", len(tt.expected), len(files))
			}

			for i, file := range files {
				if i < len(tt.expected) && file != tt.expected[i] {
					t.Errorf("FindFiles() file %d = %v, want %v", i, file, tt.expected[i])
				}
			}
		})
	}
}

func TestShouldExclude(t *testing.T) {
	tests := []struct {
		name     string
		path     string
		excludes []string
		expected bool
	}{
		{"no excludes", "test.go", []string{}, false},
		{"empty excludes", "test.go", []string{""}, false},
		{"exact match", "test.go", []string{"test.go"}, true},
		{"pattern match", "test_*.go", []string{"test_*.go"}, true},
		{"no match", "other.go", []string{"test.go"}, false},
		{
			name:     "directory match",
			path:     "vendor/lib.go",
			excludes: []string{"vendor"},
			expected: false,
		}, // filepath.Match doesn't match directories this way
		{"base directory match", "lib.go", []string{"lib.go"}, true},
		{"multiple patterns", "test.go", []string{"*.py", "test.go"}, true},
		{"invalid pattern", "test.go", []string{"[invalid"}, false},
		{"complex pattern", "internal/test/file.go", []string{"internal/*/file.go"}, true},
		{"case sensitive", "Test.go", []string{"test.go"}, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ShouldExclude(tt.path, tt.excludes)
			if result != tt.expected {
				t.Errorf("ShouldExclude(%s, %v) = %v, want %v", tt.path, tt.excludes, result, tt.expected)
			}
		})
	}
}

func TestShouldExclude_EdgeCases(t *testing.T) {
	tests := []struct {
		name     string
		path     string
		excludes []string
		expected bool
	}{
		{"empty path", "", []string{"*"}, true},
		{"empty pattern", "test.go", []string{""}, false},
		{"star pattern", "any_file.go", []string{"*"}, true}, {
			name:     "question mark",
			path:     "file.go",
			excludes: []string{"file.?"},
			expected: false}, // filepath.Match doesn't support ? this way
		{
			name:     "character class",
			path:     "file.go",
			excludes: []string{"file.[go]"},
			expected: false}, // filepath.Match doesn't support character classes
		{"nested pattern", "a/b/c/file.go", []string{"a/*/c/file.go"}, true},
		{"absolute path", "/absolute/path/file.go", []string{"*.go"}, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ShouldExclude(tt.path, tt.excludes)
			if result != tt.expected {
				t.Errorf("ShouldExclude(%s, %v) = %v, want %v", tt.path, tt.excludes, result, tt.expected)
			}
		})
	}
}

func TestMockDetector_Interface(t *testing.T) {
	// Verify that DetectorMock implements Detector interface
	var _ Detector = &DetectorMock{name: "test", severity: SeverityT1}

	detector := &DetectorMock{name: "test-detector", severity: SeverityT2}

	ctx := context.Background()
	findings, err := detector.Detect(ctx, "/test/path", &Config{})

	if err != nil {
		t.Errorf("MockDetector.Detect() unexpected error: %v", err)
	}

	if len(findings) != 1 {
		t.Errorf("DetectorMock.Detect() expected 1 finding, got %d", len(findings))
	}

	if findings[0].Type != "mock_finding" {
		t.Errorf("DetectorMock.Detect() finding type = %v, want mock_finding", findings[0].Type)
	}

	if findings[0].Severity != SeverityT2 {
		t.Errorf("DetectorMock.Detect() finding severity = %v, want T2", findings[0].Severity)
	}
}

func TestLanguageDetectorMock_Interface(t *testing.T) {
	// Verify that LanguageDetectorMock implements LanguageDetector interface
	var _ LanguageDetector = &LanguageDetectorMock{
		DetectorMock:       &DetectorMock{name: "test", severity: SeverityT1},
		supportedLanguages: []string{"go", "python"},
	}

	detector := &LanguageDetectorMock{
		DetectorMock:       &DetectorMock{name: "test-lang", severity: SeverityT3},
		supportedLanguages: []string{"go", "python", "javascript"},
	}

	if len(detector.SupportedLanguages()) != 3 {
		t.Errorf("LanguageDetectorMock.SupportedLanguages() expected 3 languages, got %d", len(detector.SupportedLanguages()))
	}

	ctx := context.Background()
	files := []string{"file1.go", "file2.py"}

	functions, err := detector.ExtractFunctions(ctx, files)
	if err != nil {
		t.Errorf("LanguageDetectorMock.ExtractFunctions() unexpected error: %v", err)
	}

	if len(functions) != 2 {
		t.Errorf("LanguageDetectorMock.ExtractFunctions() expected 2 functions, got %d", len(functions))
	}

	classes, err := detector.ExtractClasses(ctx, files)
	if err != nil {
		t.Errorf("LanguageDetectorMock.ExtractClasses() unexpected error: %v", err)
	}

	if len(classes) != 2 {
		t.Errorf("LanguageDetectorMock.ExtractClasses() expected 2 classes, got %d", len(classes))
	}
}

func TestMockFileFinder_Interface(t *testing.T) {
	// Verify that FileFinderMock implements FileFinder interface
	var _ FileFinder = &FileFinderMock{files: []string{"test.go"}}

	finder := &FileFinderMock{files: []string{"file1.go", "file2.go"}}

	files, err := finder.FindFiles("/test/path", "go")
	if err != nil {
		t.Errorf("FileFinderMock.FindFiles() unexpected error: %v", err)
	}

	if len(files) != 2 {
		t.Errorf("FileFinderMock.FindFiles() expected 2 files, got %d", len(files))
	}

	if !finder.IsSourceFile("test.go", "go") {
		t.Error("FileFinderMock.IsSourceFile() should return true")
	}
}

func TestBaseDetector_Integration(t *testing.T) {
	// Test BaseDetector with real mock implementations
	finder := &FileFinderMock{files: []string{"main.go", "utils.go"}}
	detector := NewBaseDetector("integration-test", SeverityT2, finder)

	// Test all methods
	if detector.Name() != "integration-test" {
		t.Errorf("Integration test: Name() = %v, want integration-test", detector.Name())
	}

	if detector.Severity() != SeverityT2 {
		t.Errorf("Integration test: Severity() = %v, want T2", detector.Severity())
	}

	files, err := detector.FindFiles("/project", "go")
	if err != nil {
		t.Errorf("Integration test: FindFiles() unexpected error: %v", err)
	}

	if len(files) != 2 {
		t.Errorf("Integration test: FindFiles() expected 2 files, got %d", len(files))
	}
}
