package quality

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

func TestGetSupportedLanguages(t *testing.T) {
	languages := GetSupportedLanguages()

	if len(languages) == 0 {
		t.Error("GetSupportedLanguages() should return at least one language")
	}

	expectedLanguages := []string{
		"go", "typescript", "python", "java", "rust",
		"javascript", "csharp", "dart",
	}

	if len(languages) != len(expectedLanguages) {
		t.Errorf("GetSupportedLanguages() expected %d languages, got %d", len(expectedLanguages), len(languages))
	}

	// Check that all expected languages are present
	languageMap := make(map[string]bool)
	for _, lang := range languages {
		languageMap[lang.Name] = true
	}

	for _, expected := range expectedLanguages {
		if !languageMap[expected] {
			t.Errorf("GetSupportedLanguages() missing expected language: %s", expected)
		}
	}

	// Verify Go language configuration
	var goConfig *LanguageConfig
	for _, lang := range languages {
		if lang.Name == "go" {
			goConfig = &lang
			break
		}
	}

	if goConfig == nil {
		t.Error("GetSupportedLanguages() should include Go language")
		return
	}

	expectedGoExtensions := []string{".go"}
	if len(goConfig.Extensions) != len(expectedGoExtensions) {
		t.Errorf("Go expected %d extensions, got %d", len(expectedGoExtensions), len(goConfig.Extensions))
	}

	for i, ext := range goConfig.Extensions {
		if ext != expectedGoExtensions[i] {
			t.Errorf("Go extension %d expected %s, got %s", i, expectedGoExtensions[i], ext)
		}
	}

	expectedGoMarkers := []string{"go.mod", "go.sum"}
	if len(goConfig.MarkerFiles) != len(expectedGoMarkers) {
		t.Errorf("Go expected %d marker files, got %d", len(expectedGoMarkers), len(goConfig.MarkerFiles))
	}

	if goConfig.DefaultSrc != "." {
		t.Errorf("Go expected default src '.', got %s", goConfig.DefaultSrc)
	}
}

func TestNewDefaultFileFinder(t *testing.T) {
	finder := NewDefaultFileFinder()

	if finder == nil {
		t.Error("NewDefaultFileFinder() should not return nil")
	}

	// Test that it implements the interface
	var _ FileFinder = finder
}

func TestDefaultFileFinder_FindFiles(t *testing.T) {
	// Create temporary directory for testing
	tmpDir, err := os.MkdirTemp("", "filefinder_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	finder := NewDefaultFileFinder()

	// Create test files
	testFiles := map[string]string{
		"main.go":     "package main",
		"utils.go":    "package utils",
		"test.ts":     "export function test() {}",
		"app.py":      "print('hello')",
		"Main.java":   "public class Main {}",
		"lib.rs":      "fn main() {}",
		"script.js":   "console.log('hello')",
		"Program.cs":  "using System;",
		"main.dart":   "void main() {}",
		"readme.md":   "# README",
		"config.json": "{}",
	}

	for file, content := range testFiles {
		fullPath := filepath.Join(tmpDir, file)
		err := os.WriteFile(fullPath, []byte(content), 0644)
		if err != nil {
			t.Fatalf("Failed to create test file %s: %v", file, err)
		}
	}

	// Create subdirectory with hidden folder
	os.MkdirAll(filepath.Join(tmpDir, ".hidden"), 0755)
	os.WriteFile(filepath.Join(tmpDir, ".hidden", "hidden.go"), []byte("package hidden"), 0644)

	// Create node_modules directory (should be skipped)
	os.MkdirAll(filepath.Join(tmpDir, "node_modules"), 0755)
	os.WriteFile(filepath.Join(tmpDir, "node_modules", "index.js"), []byte("module code"), 0644)

	tests := []struct {
		name     string
		language string
		expected int
	}{
		{"go files", "go", 2},
		{"typescript files", "typescript", 1},
		{"python files", "python", 1},
		{"java files", "java", 1},
		{"rust files", "rust", 1},
		{"javascript files", "javascript", 1},
		{"csharp files", "csharp", 1},
		{"dart files", "dart", 1},
		{"unknown language defaults to go", "unknown", 2},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			files, err := finder.FindFiles(tmpDir, tt.language)
			if err != nil {
				t.Errorf("FindFiles() failed: %v", err)
			}
			if len(files) != tt.expected {
				t.Errorf("FindFiles() expected %d files, got %d", tt.expected, len(files))
			}
		})
	}
}

func TestDefaultFileFinder_FindFiles_EmptyDirectory(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "filefinder_empty_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	finder := NewDefaultFileFinder()
	files, err := finder.FindFiles(tmpDir, "go")

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

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

func TestDefaultFileFinder_FindFiles_DotPathRootNotSkipped(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "filefinder_dot_root_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	if err := os.WriteFile(filepath.Join(tmpDir, "main.go"), []byte("package main"), 0644); err != nil {
		t.Fatalf("Failed to write go file: %v", err)
	}

	cwd, err := os.Getwd()
	if err != nil {
		t.Fatalf("Failed to get cwd: %v", err)
	}
	defer func() { _ = os.Chdir(cwd) }()

	if err := os.Chdir(tmpDir); err != nil {
		t.Fatalf("Failed to chdir: %v", err)
	}

	finder := NewDefaultFileFinder()
	files, err := finder.FindFiles(".", "go")
	if err != nil {
		t.Fatalf("FindFiles() failed: %v", err)
	}
	if len(files) != 1 {
		t.Fatalf("FindFiles('.') expected 1 file, got %d", len(files))
	}
}

func TestDefaultFileFinder_FindFiles_NonExistentPath(t *testing.T) {
	finder := NewDefaultFileFinder()
	files, err := finder.FindFiles("/non/existent/path", "go")

	if err == nil {
		t.Error("FindFiles() should fail for non-existent path")
	}

	if len(files) != 0 {
		t.Errorf("FindFiles() expected 0 files for error case, got %d", len(files))
	}
}

func TestDefaultFileFinder_IsSourceFile(t *testing.T) {
	finder := NewDefaultFileFinder()

	tests := []struct {
		name     string
		path     string
		language string
		expected bool
	}{
		{"go file", "main.go", "go", true},
		{"typescript file", "app.ts", "typescript", true},
		{"tsx file", "component.tsx", "typescript", true},
		{"python file", "script.py", "python", true},
		{"java file", "Main.java", "java", true},
		{"rust file", "lib.rs", "rust", true},
		{"javascript file", "app.js", "javascript", true},
		{"jsx file", "component.jsx", "javascript", true},
		{"csharp file", "Program.cs", "csharp", true},
		{"dart file", "main.dart", "dart", true},
		{"markdown file", "readme.md", "go", false},
		{"json file", "config.json", "go", false},
		{"text file", "notes.txt", "go", false},
		{"unknown language defaults to go", "script.rb", "ruby", false},
	}

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

func TestDetectLanguage(t *testing.T) {
	// Create temporary directory for testing
	tmpDir, err := os.MkdirTemp("", "detect_language_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	tests := []struct {
		name     string
		setup    func() string
		expected string
	}{
		{
			name: "go project with go.mod",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "go_test")
				os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test"), 0644)
				return dir
			},
			expected: "go",
		},
		{
			name: "typescript project with package.json",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "ts_test")
				os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}"), 0644)
				return dir
			},
			expected: "typescript",
		},
		{
			name: "typescript project with tsconfig.json",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "tsconfig_test")
				os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte("{}"), 0644)
				return dir
			},
			expected: "typescript",
		},
		{
			name: "python project with requirements.txt",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "py_test")
				os.WriteFile(filepath.Join(dir, "requirements.txt"), []byte("flask"), 0644)
				return dir
			},
			expected: "python",
		},
		{
			name: "python project with setup.py",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "setup_test")
				os.WriteFile(filepath.Join(dir, "setup.py"), []byte("from setuptools import setup"), 0644)
				return dir
			},
			expected: "python",
		},
		{
			name: "python project with pyproject.toml",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "pyproject_test")
				os.WriteFile(filepath.Join(dir, "pyproject.toml"), []byte("[build-system]"), 0644)
				return dir
			},
			expected: "python",
		},
		{
			name: "java project with pom.xml",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "java_test")
				os.WriteFile(filepath.Join(dir, "pom.xml"), []byte("<project></project>"), 0644)
				return dir
			},
			expected: "java",
		},
		{
			name: "java project with build.gradle",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "gradle_test")
				os.WriteFile(filepath.Join(dir, "build.gradle"), []byte("plugins {}"), 0644)
				return dir
			},
			expected: "java",
		},
		{
			name: "rust project with Cargo.toml",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "rust_test")
				os.WriteFile(filepath.Join(dir, "Cargo.toml"), []byte("[package]"), 0644)
				return dir
			},
			expected: "rust",
		},
		{
			name: "javascript project with package.json",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "js_test")
				os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}"), 0644)
				return dir
			},
			expected: "typescript", // TypeScript comes before JavaScript in the list
		},
		{
			name: "csharp project with .csproj file",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "cs_test")
				os.WriteFile(filepath.Join(dir, "Project.csproj"), []byte("<Project></Project>"), 0644)
				return dir
			},
			expected: "csharp",
		},
		{
			name: "csharp project with .sln file",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "sln_test")
				os.WriteFile(filepath.Join(dir, "Solution.sln"), []byte("Microsoft Visual Studio Solution File"), 0644)
				return dir
			},
			expected: "csharp",
		},
		{
			name: "dart project with pubspec.yaml",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "dart_test")
				os.WriteFile(filepath.Join(dir, "pubspec.yaml"), []byte("name: test"), 0644)
				return dir
			},
			expected: "dart",
		},
		{
			name: "no markers defaults to go",
			setup: func() string {
				dir, _ := os.MkdirTemp("", "empty_test")
				return dir
			},
			expected: "go",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			dir := tt.setup()
			defer os.RemoveAll(dir)

			detected := DetectLanguage(dir)
			if detected != tt.expected {
				t.Errorf("DetectLanguage() = %v, want %v", detected, tt.expected)
			}
		})
	}
}

func TestDetectLanguage_NonExistentPath(t *testing.T) {
	result := DetectLanguage("/non/existent/path")
	if result != "go" {
		t.Errorf("DetectLanguage() should default to 'go' for non-existent path, got %s", result)
	}
}

func TestDetectLanguage_MultipleMarkers(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "multiple_markers_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	// Create multiple marker files - should detect the first one in order
	os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module test"), 0644)
	os.WriteFile(filepath.Join(tmpDir, "package.json"), []byte("{}"), 0644)

	detected := DetectLanguage(tmpDir)
	if detected != "go" {
		t.Errorf("DetectLanguage() should detect 'go' (first in order), got %s", detected)
	}
}

func TestLanguageConfig_Structure(t *testing.T) {
	languages := GetSupportedLanguages()

	for _, lang := range languages {
		if lang.Name == "" {
			t.Error("LanguageConfig.Name should not be empty")
		}

		if len(lang.Extensions) == 0 {
			t.Errorf("LanguageConfig %s should have at least one extension", lang.Name)
		}

		if len(lang.MarkerFiles) == 0 {
			t.Errorf("LanguageConfig %s should have at least one marker file", lang.Name)
		}

		if lang.DefaultSrc == "" {
			t.Errorf("LanguageConfig %s should have a default source directory", lang.Name)
		}

		// Verify extensions start with dot
		for _, ext := range lang.Extensions {
			if !strings.HasPrefix(ext, ".") {
				t.Errorf("LanguageConfig %s extension %s should start with '.'", lang.Name, ext)
			}
		}
	}
}
