package gocmd

import (
	"bufio"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/host-uk/core/pkg/cli"
	"github.com/host-uk/core/pkg/i18n"
)

var (
	fmtFix   bool
	fmtDiff  bool
	fmtCheck bool
	fmtAll   bool
)

func addGoFmtCommand(parent *cli.Command) {
	fmtCmd := &cli.Command{
		Use:   "fmt",
		Short: "Format Go code",
		Long:  "Format Go code using goimports or gofmt. By default only checks changed files.",
		RunE: func(cmd *cli.Command, args []string) error {
			// Get list of files to check
			var files []string
			if fmtAll {
				// Check all Go files
				files = []string{"."}
			} else {
				// Only check changed Go files (git-aware)
				files = getChangedGoFiles()
				if len(files) == 0 {
					cli.Print("%s\n", i18n.T("cmd.go.fmt.no_changes"))
					return nil
				}
			}

			// Validate flag combinations
			if fmtCheck && fmtFix {
				return cli.Err("--check and --fix are mutually exclusive")
			}

			fmtArgs := []string{}
			if fmtFix {
				fmtArgs = append(fmtArgs, "-w")
			}
			if fmtDiff {
				fmtArgs = append(fmtArgs, "-d")
			}
			if !fmtFix && !fmtDiff {
				fmtArgs = append(fmtArgs, "-l")
			}
			fmtArgs = append(fmtArgs, files...)

			// Try goimports first, fall back to gofmt
			var execCmd *exec.Cmd
			if _, err := exec.LookPath("goimports"); err == nil {
				execCmd = exec.Command("goimports", fmtArgs...)
			} else {
				execCmd = exec.Command("gofmt", fmtArgs...)
			}

			// For --check mode, capture output to detect unformatted files
			if fmtCheck {
				output, err := execCmd.CombinedOutput()
				if err != nil {
					_, _ = os.Stderr.Write(output)
					return err
				}
				if len(output) > 0 {
					_, _ = os.Stdout.Write(output)
					return cli.Err("files need formatting (use --fix)")
				}
				return nil
			}

			execCmd.Stdout = os.Stdout
			execCmd.Stderr = os.Stderr
			return execCmd.Run()
		},
	}

	fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, i18n.T("common.flag.fix"))
	fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, i18n.T("common.flag.diff"))
	fmtCmd.Flags().BoolVar(&fmtCheck, "check", false, i18n.T("cmd.go.fmt.flag.check"))
	fmtCmd.Flags().BoolVar(&fmtAll, "all", false, i18n.T("cmd.go.fmt.flag.all"))

	parent.AddCommand(fmtCmd)
}

// getChangedGoFiles returns Go files that have been modified, staged, or are untracked.
func getChangedGoFiles() []string {
	var files []string

	// Get modified and staged files
	cmd := exec.Command("git", "diff", "--name-only", "--diff-filter=ACMR", "HEAD")
	output, err := cmd.Output()
	if err == nil {
		files = append(files, filterGoFiles(string(output))...)
	}

	// Get untracked files
	cmd = exec.Command("git", "ls-files", "--others", "--exclude-standard")
	output, err = cmd.Output()
	if err == nil {
		files = append(files, filterGoFiles(string(output))...)
	}

	// Deduplicate
	seen := make(map[string]bool)
	var unique []string
	for _, f := range files {
		if !seen[f] {
			seen[f] = true
			// Verify file exists (might have been deleted)
			if _, err := os.Stat(f); err == nil {
				unique = append(unique, f)
			}
		}
	}

	return unique
}

// filterGoFiles filters a newline-separated list of files to only include .go files.
func filterGoFiles(output string) []string {
	var goFiles []string
	scanner := bufio.NewScanner(strings.NewReader(output))
	for scanner.Scan() {
		file := strings.TrimSpace(scanner.Text())
		if file != "" && filepath.Ext(file) == ".go" {
			goFiles = append(goFiles, file)
		}
	}
	return goFiles
}

var (
	lintFix bool
	lintAll bool
)

func addGoLintCommand(parent *cli.Command) {
	lintCmd := &cli.Command{
		Use:   "lint",
		Short: "Run golangci-lint",
		Long:  "Run golangci-lint for comprehensive static analysis. By default only lints changed files.",
		RunE: func(cmd *cli.Command, args []string) error {
			lintArgs := []string{"run"}
			if lintFix {
				lintArgs = append(lintArgs, "--fix")
			}

			if !lintAll {
				// Use --new-from-rev=HEAD to only report issues in uncommitted changes
				// This is golangci-lint's native way to handle incremental linting
				lintArgs = append(lintArgs, "--new-from-rev=HEAD")
			}

			// Always lint all packages
			lintArgs = append(lintArgs, "./...")

			execCmd := exec.Command("golangci-lint", lintArgs...)
			execCmd.Stdout = os.Stdout
			execCmd.Stderr = os.Stderr
			return execCmd.Run()
		},
	}

	lintCmd.Flags().BoolVar(&lintFix, "fix", false, i18n.T("common.flag.fix"))
	lintCmd.Flags().BoolVar(&lintAll, "all", false, i18n.T("cmd.go.lint.flag.all"))

	parent.AddCommand(lintCmd)
}
