package golang

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

	"git.linuxforward.com/byop/byop-engine/analyzer/templates"
)

type Golang struct {
	// Add fields as needed for Golang analysis
}

// Name returns the name of the Golang technology stack.
func (g *Golang) Name() string {
	return "Golang"
}

// Analyze analyzes the codebase and returns a guessed technology stack for Golang.
func (g *Golang) Analyze(codebasePath string) (bool, error) {
	// Verify if the codebasePath is valid and contains Go files
	if codebasePath == "" {
		return false, fmt.Errorf("codebase path cannot be empty")
	}

	// Check if the codebase exists and contains Go files
	_, err := os.Stat(codebasePath)
	if os.IsNotExist(err) {
		return false, fmt.Errorf("codebase path does not exist: %s", codebasePath)
	}

	// Enhanced detection logic inspired by Railway Railpack
	return g.isGoMod(codebasePath) || g.isGoWorkspace(codebasePath) || g.hasMainGo(codebasePath), nil
}

// GenerateDockerfile generates a Dockerfile for Golang projects based on analysis
func (g *Golang) GenerateDockerfile(codebasePath string) (string, error) {
	// Analyze the Go project
	analysis, err := g.analyzeGoProject(codebasePath)
	if err != nil {
		return "", fmt.Errorf("failed to analyze Go project: %w", err)
	}

	// Check for file existence
	hasGoMod := g.fileExists(filepath.Join(codebasePath, "go.mod"))
	hasGoSum := g.fileExists(filepath.Join(codebasePath, "go.sum"))
	hasVendor := g.dirExists(filepath.Join(codebasePath, "vendor"))

	// Determine build tools needed
	buildTools := []string{"git"} // Always need git for go get
	needsBuildTools := true

	// Determine runtime dependencies
	runtimeDeps := []string{}
	needsRuntimeDeps := false
	runtimeImage := "alpine:latest"

	if analysis.RequiresCACerts {
		runtimeDeps = append(runtimeDeps, "ca-certificates")
		needsRuntimeDeps = true
	}
	if analysis.RequiresTimezone {
		runtimeDeps = append(runtimeDeps, "tzdata")
		needsRuntimeDeps = true
	}
	if analysis.CGOEnabled {
		runtimeDeps = append(runtimeDeps, "libc6-compat")
		needsRuntimeDeps = true
		buildTools = append(buildTools, "build-base")
	}

	// If no runtime deps needed, we can use scratch
	if !needsRuntimeDeps {
		runtimeImage = "scratch"
	}

	// Create template data
	templateData := DockerfileTemplateData{
		AppName:             analysis.AppName,
		BinaryName:          analysis.AppName,
		GoVersion:           g.getGoVersion(codebasePath),
		Port:                analysis.Port,
		BuildCommand:        g.getBuildCommand(analysis, codebasePath),
		HasGoMod:            hasGoMod,
		HasGoSum:            hasGoSum,
		HasVendor:           hasVendor,
		CGOEnabled:          analysis.CGOEnabled,
		NeedsBuildTools:     needsBuildTools,
		BuildTools:          buildTools,
		NeedsRuntimeDeps:    needsRuntimeDeps,
		RuntimeDeps:         runtimeDeps,
		RuntimeImage:        runtimeImage,
		HealthCheckEndpoint: "/health", // Default health check endpoint
	}

	// Create template engine
	engine, err := templates.NewTemplateEngine()
	if err != nil {
		return "", fmt.Errorf("failed to create template engine: %w", err)
	}

	// Render template
	dockerfile, err := engine.Render("golang", templateData)
	if err != nil {
		return "", fmt.Errorf("failed to render Golang template: %w", err)
	}

	return dockerfile, nil
}

// DockerfileTemplateData represents the data passed to Golang Dockerfile templates
type DockerfileTemplateData struct {
	AppName             string   `json:"app_name"`
	BinaryName          string   `json:"binary_name"`
	GoVersion           string   `json:"go_version"`
	Port                int      `json:"port"`
	BuildCommand        string   `json:"build_command"`
	HasGoMod            bool     `json:"has_go_mod"`
	HasGoSum            bool     `json:"has_go_sum"`
	HasVendor           bool     `json:"has_vendor"`
	CGOEnabled          bool     `json:"cgo_enabled"`
	NeedsBuildTools     bool     `json:"needs_build_tools"`
	BuildTools          []string `json:"build_tools"`
	NeedsRuntimeDeps    bool     `json:"needs_runtime_deps"`
	RuntimeDeps         []string `json:"runtime_deps"`
	RuntimeImage        string   `json:"runtime_image"`
	HealthCheckEndpoint string   `json:"health_check_endpoint,omitempty"`
}

// GoProjectAnalysis contains analysis results for a Go project
type GoProjectAnalysis struct {
	GoVersion        string            `json:"go_version"`
	AppName          string            `json:"app_name"`
	MainPackage      string            `json:"main_package"`
	Port             int               `json:"port"`
	Modules          []string          `json:"modules"`
	BuildTags        []string          `json:"build_tags"`
	RequiresCACerts  bool              `json:"requires_ca_certs"`
	RequiresTimezone bool              `json:"requires_timezone"`
	EnvVars          map[string]string `json:"env_vars"`
	CGOEnabled       bool              `json:"cgo_enabled"` // New field for CGO
}

// analyzeGoProject analyzes a Go project to understand its structure and requirements
func (g *Golang) analyzeGoProject(codebasePath string) (*GoProjectAnalysis, error) {
	analysis := &GoProjectAnalysis{
		Port:       8080, // Default
		EnvVars:    make(map[string]string),
		Modules:    []string{},
		CGOEnabled: os.Getenv("CGO_ENABLED") == "1", // Check CGO_ENABLED env var
	}

	// Read go.mod to get module name and Go version
	goModPath := filepath.Join(codebasePath, "go.mod")
	if content, err := os.ReadFile(goModPath); err == nil {
		lines := strings.Split(string(content), "\n")
		for _, line := range lines {
			line = strings.TrimSpace(line)
			if strings.HasPrefix(line, "module ") {
				parts := strings.Fields(line)
				if len(parts) > 1 {
					moduleName := parts[1]
					// Extract app name from module path
					pathParts := strings.Split(moduleName, "/")
					analysis.AppName = pathParts[len(pathParts)-1]
				}
			} else if strings.HasPrefix(line, "go ") {
				parts := strings.Fields(line)
				if len(parts) > 1 {
					analysis.GoVersion = parts[1]
				}
			}
		}
	}

	// Find main package
	analysis.MainPackage = g.findMainPackage(codebasePath)

	// Analyze imports to determine requirements
	g.analyzeImports(codebasePath, analysis)

	// Try to detect port from common patterns
	analysis.Port = g.detectPort(codebasePath)

	// Set default app name if not found
	if analysis.AppName == "" {
		analysis.AppName = filepath.Base(codebasePath)
		if analysis.AppName == "." || analysis.AppName == "" {
			analysis.AppName = "goapp"
		}
	}

	return analysis, nil
}

func (g *Golang) findMainPackage(codebasePath string) string {
	// Strategy inspired by Railway Railpack - multiple fallback approaches

	// 1. Check for main.go in root (most common simple case)
	if g.hasMainGo(codebasePath) && g.hasRootGoFiles(codebasePath) {
		return "."
	}

	// 2. Look for cmd directory structure (standard Go layout)
	if dirs, err := g.findCmdDirectories(codebasePath); err == nil && len(dirs) > 0 {
		// Try to find the first directory with a main function
		for _, dir := range dirs {
			if g.hasMainFunction(filepath.Join(codebasePath, dir)) {
				return "./" + dir // Add ./ prefix for local path
			}
		}
		// Fallback to first cmd directory if none have main function
		return "./" + dirs[0]
	}

	// 3. Check if it's a Go workspace with multiple modules
	if g.isGoWorkspace(codebasePath) {
		packages := g.getGoWorkspacePackages(codebasePath)
		for _, pkg := range packages {
			pkgPath := filepath.Join(codebasePath, pkg)
			if g.hasMainGo(pkgPath) || g.hasMainFunction(pkgPath) {
				return "./" + pkg
			}
		}
	}

	// 4. Look for any directory with a main function as final fallback
	if entries, err := os.ReadDir(codebasePath); err == nil {
		for _, entry := range entries {
			if entry.IsDir() && entry.Name() != ".git" && entry.Name() != "vendor" && entry.Name() != "configs" {
				dirPath := filepath.Join(codebasePath, entry.Name())
				if g.hasMainFunction(dirPath) {
					return "./" + entry.Name()
				}
			}
		}
	}

	// 5. Default fallback
	return "."
}

// hasMainFunction checks if a directory contains Go files with a main function
func (g *Golang) hasMainFunction(dirPath string) bool {
	files, err := os.ReadDir(dirPath)
	if err != nil {
		return false
	}

	for _, file := range files {
		if strings.HasSuffix(file.Name(), ".go") && !file.IsDir() {
			content, err := os.ReadFile(filepath.Join(dirPath, file.Name()))
			if err != nil {
				continue
			}

			contentStr := string(content)
			// Look for main function or main.go file
			if strings.Contains(contentStr, "func main(") || file.Name() == "main.go" {
				return true
			}
		}
	}
	return false
}

func (g *Golang) analyzeImports(codebasePath string, analysis *GoProjectAnalysis) {
	// Walk through Go files and analyze imports
	filepath.Walk(codebasePath, func(path string, info os.FileInfo, err error) error {
		if err != nil || !strings.HasSuffix(path, ".go") {
			return nil
		}

		content, err := os.ReadFile(path)
		if err != nil {
			return nil
		}

		contentStr := string(content)

		// Check for common patterns that require CA certs
		if strings.Contains(contentStr, "crypto/tls") ||
			strings.Contains(contentStr, "net/http") ||
			strings.Contains(contentStr, "github.com/") {
			analysis.RequiresCACerts = true
		}

		// Check for timezone requirements
		if strings.Contains(contentStr, "time.LoadLocation") ||
			strings.Contains(contentStr, "time.ParseInLocation") {
			analysis.RequiresTimezone = true
		}

		// Add common modules
		if strings.Contains(contentStr, "github.com/gin-gonic/gin") {
			analysis.Modules = append(analysis.Modules, "gin")
		}
		if strings.Contains(contentStr, "github.com/gorilla/mux") {
			analysis.Modules = append(analysis.Modules, "gorilla-mux")
		}
		if strings.Contains(contentStr, "github.com/labstack/echo") {
			analysis.Modules = append(analysis.Modules, "echo")
		}

		return nil
	})
}

func (g *Golang) detectPort(codebasePath string) int {
	defaultPort := 8080

	// Common port detection patterns
	patterns := []string{
		":8080",
		":3000",
		":8000",
		"PORT",
		"HTTP_PORT",
	}

	filepath.Walk(codebasePath, func(path string, info os.FileInfo, err error) error {
		if err != nil || !strings.HasSuffix(path, ".go") {
			return nil
		}

		content, err := os.ReadFile(path)
		if err != nil {
			return nil
		}

		contentStr := string(content)
		for _, pattern := range patterns {
			if strings.Contains(contentStr, pattern) {
				// Try to extract actual port number
				if strings.Contains(pattern, ":") {
					portStr := strings.TrimPrefix(pattern, ":")
					if port := g.parsePort(portStr); port > 0 {
						defaultPort = port
						return filepath.SkipAll
					}
				}
			}
		}
		return nil
	})

	return defaultPort
}

func (g *Golang) parsePort(portStr string) int {
	// Simple port parsing - in production you'd want more robust parsing
	switch portStr {
	case "8080":
		return 8080
	case "3000":
		return 3000
	case "8000":
		return 8000
	default:
		return 0
	}
}

func (g *Golang) fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func (g *Golang) dirExists(dirpath string) bool {
	info, err := os.Stat(dirpath)
	return err == nil && info.IsDir()
}

// Helper methods inspired by Railway Railpack

func (g *Golang) isGoMod(codebasePath string) bool {
	_, err := os.Stat(filepath.Join(codebasePath, "go.mod"))
	return err == nil
}

func (g *Golang) isGoWorkspace(codebasePath string) bool {
	_, err := os.Stat(filepath.Join(codebasePath, "go.work"))
	return err == nil
}

func (g *Golang) hasMainGo(codebasePath string) bool {
	_, err := os.Stat(filepath.Join(codebasePath, "main.go"))
	return err == nil
}

func (g *Golang) hasRootGoFiles(codebasePath string) bool {
	files, err := filepath.Glob(filepath.Join(codebasePath, "*.go"))
	if err != nil {
		return false
	}
	return len(files) > 0
}

func (g *Golang) findCmdDirectories(codebasePath string) ([]string, error) {
	cmdDir := filepath.Join(codebasePath, "cmd")
	entries, err := os.ReadDir(cmdDir)
	if err != nil {
		return nil, err
	}

	var dirs []string
	for _, entry := range entries {
		if entry.IsDir() {
			dirs = append(dirs, filepath.Join("cmd", entry.Name()))
		}
	}
	return dirs, nil
}

func (g *Golang) getGoWorkspacePackages(codebasePath string) []string {
	var packages []string

	err := filepath.Walk(codebasePath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil // Continue walking even if there's an error
		}

		if info.Name() == "go.mod" {
			relPath, err := filepath.Rel(codebasePath, filepath.Dir(path))
			if err != nil {
				return nil
			}

			// Skip the root go.mod
			if relPath != "." {
				packages = append(packages, relPath)
			}
		}
		return nil
	})

	if err != nil {
		return packages
	}

	return packages
}

// Enhanced build strategy inspired by Railway Railpack
func (g *Golang) getBuildCommand(analysis *GoProjectAnalysis, codebasePath string) string {
	appName := analysis.AppName
	if appName == "" {
		appName = "app"
	}

	flags := "-w -s"
	if !analysis.CGOEnabled {
		flags = "-w -s -extldflags \"-static\""
	}

	baseBuildCmd := fmt.Sprintf("go build -ldflags=\"%s\" -o %s", flags, appName)

	// Strategy 1: Use explicit main package if detected
	if analysis.MainPackage != "" && analysis.MainPackage != "." {
		return fmt.Sprintf("%s %s", baseBuildCmd, analysis.MainPackage)
	}

	// Strategy 2: Check for root Go files
	if g.hasRootGoFiles(codebasePath) && g.isGoMod(codebasePath) {
		return baseBuildCmd
	}

	// Strategy 3: Try cmd directories
	if dirs, err := g.findCmdDirectories(codebasePath); err == nil && len(dirs) > 0 {
		return fmt.Sprintf("%s ./%s", baseBuildCmd, dirs[0])
	}

	// Strategy 4: Go workspace
	if g.isGoWorkspace(codebasePath) {
		packages := g.getGoWorkspacePackages(codebasePath)
		for _, pkg := range packages {
			if g.hasMainGo(filepath.Join(codebasePath, pkg)) {
				return fmt.Sprintf("%s ./%s", baseBuildCmd, pkg)
			}
		}
	}

	// Strategy 5: Fallback to main.go if present
	if g.hasMainGo(codebasePath) {
		return fmt.Sprintf("%s main.go", baseBuildCmd)
	}

	// Default
	return baseBuildCmd
}

// Enhanced Go version detection
func (g *Golang) getGoVersion(codebasePath string) string {
	// First check go.mod file
	if goModContents, err := os.ReadFile(filepath.Join(codebasePath, "go.mod")); err == nil {
		lines := strings.Split(string(goModContents), "\n")
		for _, line := range lines {
			line = strings.TrimSpace(line)
			if strings.HasPrefix(line, "go ") {
				parts := strings.Fields(line)
				if len(parts) > 1 {
					return parts[1]
				}
			}
		}
	}

	// Check environment variable
	if envVersion := os.Getenv("GO_VERSION"); envVersion != "" {
		return envVersion
	}

	// Default version
	return "1.23"
}
