package golang

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

func TestGolang(t *testing.T) {
	// Create a new Golang stack instance
	golangStack := &Golang{}

	// Test the Name method
	expectedName := "Golang"
	if golangStack.Name() != expectedName {
		t.Errorf("Expected name %s, got %s", expectedName, golangStack.Name())
	}
}

func TestAnalyze(t *testing.T) {
	golangStack := &Golang{}

	tests := []struct {
		name     string
		setup    func() string
		cleanup  func(string)
		expected bool
		wantErr  bool
	}{
		{
			name: "valid go project with go.mod",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-go-project-")
				os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module test\ngo 1.21\n"), 0644)
				os.WriteFile(filepath.Join(tempDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: true,
			wantErr:  false,
		},
		{
			name: "valid go project with main.go only",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-go-project-")
				os.WriteFile(filepath.Join(tempDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: true,
			wantErr:  false,
		},
		{
			name: "non-go project",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-non-go-project-")
				os.WriteFile(filepath.Join(tempDir, "package.json"), []byte("{}"), 0644)
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: false,
			wantErr:  false,
		},
		{
			name: "empty directory",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-empty-")
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: false,
			wantErr:  false,
		},
		{
			name: "non-existent directory",
			setup: func() string {
				return "/non/existent/path"
			},
			cleanup:  func(dir string) {},
			expected: false,
			wantErr:  true,
		},
	}

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

			isGo, err := golangStack.Analyze(codebasePath)

			if tt.wantErr && err == nil {
				t.Errorf("Expected error but got none")
			}
			if !tt.wantErr && err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			if isGo != tt.expected {
				t.Errorf("Expected %v, got %v", tt.expected, isGo)
			}
		})
	}
}

func TestFindMainPackage(t *testing.T) {
	golangStack := &Golang{}

	tests := []struct {
		name     string
		setup    func() string
		cleanup  func(string)
		expected string
	}{
		{
			name: "main.go in root",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-main-root-")
				os.WriteFile(filepath.Join(tempDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: ".",
		},
		{
			name: "main.go in cmd/server",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-cmd-server-")
				cmdDir := filepath.Join(tempDir, "cmd", "server")
				os.MkdirAll(cmdDir, 0755)
				os.WriteFile(filepath.Join(cmdDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: "./cmd/server",
		},
		{
			name: "main function in cmd/web-server/app.go",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-cmd-web-server-")
				cmdDir := filepath.Join(tempDir, "cmd", "web-server")
				os.MkdirAll(cmdDir, 0755)
				os.WriteFile(filepath.Join(cmdDir, "app.go"), []byte("package main\nfunc main() {}\n"), 0644)
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: "./cmd/web-server",
		},
		{
			name: "no main package found",
			setup: func() string {
				tempDir, _ := os.MkdirTemp("", "test-no-main-")
				libDir := filepath.Join(tempDir, "lib")
				os.MkdirAll(libDir, 0755)
				os.WriteFile(filepath.Join(libDir, "helper.go"), []byte("package lib\nfunc Helper() {}\n"), 0644)
				return tempDir
			},
			cleanup:  func(dir string) { os.RemoveAll(dir) },
			expected: ".",
		},
	}

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

			result := golangStack.findMainPackage(codebasePath)
			if result != tt.expected {
				t.Errorf("Expected %q, got %q", tt.expected, result)
			}
		})
	}
}

func TestAnalyzeGoProject(t *testing.T) {
	golangStack := &Golang{}

	// Create a test project structure
	tempDir, err := os.MkdirTemp("", "test-analyze-project-")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tempDir)

	// Create go.mod
	goMod := `module github.com/example/web-server
go 1.21

require (
	github.com/gin-gonic/gin v1.9.1
)
`
	os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte(goMod), 0644)

	// Create cmd/web-server/main.go
	cmdDir := filepath.Join(tempDir, "cmd", "web-server")
	os.MkdirAll(cmdDir, 0755)
	mainGo := `package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "Hello World"})
	})
	r.Run(":8080")
}
`
	os.WriteFile(filepath.Join(cmdDir, "main.go"), []byte(mainGo), 0644)

	// Run analysis
	analysis, err := golangStack.analyzeGoProject(tempDir)
	if err != nil {
		t.Fatalf("Analysis failed: %v", err)
	}

	// Verify results
	if analysis.GoVersion != "1.21" {
		t.Errorf("Expected Go version 1.21, got %s", analysis.GoVersion)
	}

	if analysis.AppName != "web-server" {
		t.Errorf("Expected app name 'web-server', got %s", analysis.AppName)
	}

	if analysis.MainPackage != "./cmd/web-server" {
		t.Errorf("Expected main package './cmd/web-server', got %s", analysis.MainPackage)
	}

	if analysis.Port != 8080 {
		t.Errorf("Expected port 8080, got %d", analysis.Port)
	}

	if !analysis.RequiresCACerts {
		t.Error("Expected RequiresCACerts to be true (due to net/http import)")
	}

	if !contains(analysis.Modules, "gin") {
		t.Error("Expected gin module to be detected")
	}
}

func TestWebServerProjectStructure(t *testing.T) {
	// This test specifically replicates the structure from the failed build
	golangStack := &Golang{}

	tempDir, err := os.MkdirTemp("", "test-web-server-structure-")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tempDir)

	// Create the directory structure that was failing
	os.MkdirAll(filepath.Join(tempDir, ".vscode"), 0755)
	os.MkdirAll(filepath.Join(tempDir, "cmd", "web-server"), 0755)
	os.MkdirAll(filepath.Join(tempDir, "configs"), 0755)
	os.MkdirAll(filepath.Join(tempDir, "pkg", "mhttp"), 0755)
	os.MkdirAll(filepath.Join(tempDir, "scripts"), 0755)
	os.MkdirAll(filepath.Join(tempDir, "tests"), 0755)
	os.MkdirAll(filepath.Join(tempDir, "web"), 0755)

	// Create files
	os.WriteFile(filepath.Join(tempDir, "LICENSE"), []byte("MIT License"), 0644)
	os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module golang-web-server\ngo 1.18\n"), 0644)

	// Create the main file in cmd/web-server (this should be detected)
	os.WriteFile(filepath.Join(tempDir, "cmd", "web-server", "golang-web-server.go"),
		[]byte("package main\nfunc main() {}\n"), 0644)

	// Create other Go files that should NOT be chosen as main
	os.WriteFile(filepath.Join(tempDir, "pkg", "mhttp", "server.go"),
		[]byte("package mhttp\nfunc StartServer() {}\n"), 0644)
	os.WriteFile(filepath.Join(tempDir, "pkg", "mhttp", "functions.go"),
		[]byte("package mhttp\nfunc Handler() {}\n"), 0644)
	os.WriteFile(filepath.Join(tempDir, "configs", "server-config.go"),
		[]byte("package configs\nvar Config = map[string]string{}\n"), 0644)

	// Test main package detection
	mainPackage := golangStack.findMainPackage(tempDir)
	if mainPackage != "./cmd/web-server" {
		t.Errorf("Expected main package './cmd/web-server', got '%s'", mainPackage)
	}

	// Test full analysis
	analysis, err := golangStack.analyzeGoProject(tempDir)
	if err != nil {
		t.Fatalf("Analysis failed: %v", err)
	}

	if analysis.MainPackage != "./cmd/web-server" {
		t.Errorf("Expected main package './cmd/web-server', got '%s'", analysis.MainPackage)
	}

	if analysis.AppName != "golang-web-server" {
		t.Errorf("Expected app name 'golang-web-server', got '%s'", analysis.AppName)
	}

	t.Logf("✅ Correctly detected main package: %s", analysis.MainPackage)
	t.Logf("✅ Correctly detected app name: %s", analysis.AppName)
}

func TestBuildCommandGeneration(t *testing.T) {
	tests := []struct {
		name            string
		mainPackage     string
		cgoEnabled      bool
		expectedCommand string
	}{
		{
			name:            "root package",
			mainPackage:     ".",
			cgoEnabled:      false,
			expectedCommand: "go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o testapp .",
		},
		{
			name:            "cmd subdirectory",
			mainPackage:     "cmd/server",
			cgoEnabled:      false,
			expectedCommand: "go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o testapp ./cmd/server",
		},
		{
			name:            "cmd subdirectory with CGO",
			mainPackage:     "cmd/web-server",
			cgoEnabled:      true,
			expectedCommand: "go build -ldflags='-w -s' -a -installsuffix cgo -o testapp ./cmd/web-server",
		},
		{
			name:            "already has ./ prefix",
			mainPackage:     "./cmd/api",
			cgoEnabled:      false,
			expectedCommand: "go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o testapp ./cmd/api",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create test project
			tempDir, err := os.MkdirTemp("", "build-cmd-test-")
			if err != nil {
				t.Fatal(err)
			}
			defer os.RemoveAll(tempDir)

			// Create minimal structure
			os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module testapp\ngo 1.21\n"), 0644)

			// Mock the analysis result
			analysis := &GoProjectAnalysis{
				AppName:     "testapp",
				MainPackage: tt.mainPackage,
				CGOEnabled:  tt.cgoEnabled,
			}

			// Generate build command (simulate the logic from GenerateLLBAdvanced)
			appName := analysis.AppName
			if appName == "" {
				appName = "app"
			}

			var buildCmd string
			mainPackagePath := analysis.MainPackage
			if mainPackagePath != "." && !strings.HasPrefix(mainPackagePath, "./") {
				mainPackagePath = "./" + mainPackagePath
			}

			if analysis.CGOEnabled {
				buildCmd = fmt.Sprintf("go build -ldflags='-w -s' -a -installsuffix cgo -o %s %s",
					appName, mainPackagePath)
			} else {
				buildCmd = fmt.Sprintf("go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o %s %s",
					appName, mainPackagePath)
			}

			if buildCmd != tt.expectedCommand {
				t.Errorf("Expected command:\n%s\nGot:\n%s", tt.expectedCommand, buildCmd)
			}

			t.Logf("✅ Build command: %s", buildCmd)
		})
	}
}

// Helper function
func contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}
