package persistence

import (
	"bytes"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"
)

const (
	GraphDirectoryName = ".agentteam-insight"
	GraphFileName      = "knowledge-graph.json"
	MetaFileName       = "meta.json"
	ConfigFileName     = "config.json"
	FingerprintFile    = "fingerprints.json"
	DiffOverlayFile    = "diff-overlay.json"
	DomainGraphFile    = "domain-graph.json"
	IntermediateName   = "intermediate"
	TmpName            = "tmp"
	ScanResultFile     = "scan-result.json"
	BatchesFile        = "batches.json"
	BatchExistingFile  = "batch-existing.json"
	ReviewFile         = "review.json"
)

type Layout struct {
	InputRoot        string
	ProjectRoot      string
	GraphRoot        string
	IntermediateRoot string
	TmpRoot          string
	GraphPath        string
	FingerprintsPath string
	MetaPath         string
	ConfigPath       string
	DiffOverlayPath  string
	DomainGraphPath  string
}

func ResolveLayout(inputRoot string, outputRoot string) (Layout, error) {
	inputRoot = strings.TrimSpace(inputRoot)
	if inputRoot == "" {
		return Layout{}, fmt.Errorf("input root is required")
	}
	absInput, err := filepath.Abs(inputRoot)
	if err != nil {
		return Layout{}, fmt.Errorf("resolve input root: %w", err)
	}
	projectRoot := strings.TrimSpace(outputRoot)
	if projectRoot == "" {
		projectRoot = absInput
	}
	absProject, err := filepath.Abs(projectRoot)
	if err != nil {
		return Layout{}, fmt.Errorf("resolve output root: %w", err)
	}
	if redirected, ok := RedirectWorktreeRoot(absProject); ok {
		absProject = redirected
	}
	graphRoot := filepath.Join(absProject, GraphDirectoryName)
	return Layout{
		InputRoot:        absInput,
		ProjectRoot:      absProject,
		GraphRoot:        graphRoot,
		IntermediateRoot: filepath.Join(graphRoot, IntermediateName),
		TmpRoot:          filepath.Join(graphRoot, TmpName),
		GraphPath:        filepath.Join(graphRoot, GraphFileName),
		FingerprintsPath: filepath.Join(graphRoot, FingerprintFile),
		MetaPath:         filepath.Join(graphRoot, MetaFileName),
		ConfigPath:       filepath.Join(graphRoot, ConfigFileName),
		DiffOverlayPath:  filepath.Join(graphRoot, DiffOverlayFile),
		DomainGraphPath:  filepath.Join(graphRoot, DomainGraphFile),
	}, nil
}

func RedirectWorktreeRoot(root string) (string, bool) {
	gitDir, err := gitRevParse(root, "--git-dir")
	if err != nil {
		return "", false
	}
	commonDir, err := gitRevParse(root, "--git-common-dir")
	if err != nil {
		return "", false
	}
	return RedirectWorktreeRootFromGitDirs(root, gitDir, commonDir)
}

func RedirectWorktreeRootFromGitDirs(root string, gitDir string, commonDir string) (string, bool) {
	absGitDir, err := absMaybeRelative(root, strings.TrimSpace(gitDir))
	if err != nil {
		return "", false
	}
	absCommonDir, err := absMaybeRelative(root, strings.TrimSpace(commonDir))
	if err != nil {
		return "", false
	}
	if sameCleanPath(absGitDir, absCommonDir) {
		return "", false
	}
	if filepath.Base(absCommonDir) != ".git" {
		return "", false
	}
	mainRoot := filepath.Dir(absCommonDir)
	if sameCleanPath(mainRoot, root) {
		return "", false
	}
	return mainRoot, true
}

func SanitizeFilePath(inputRoot string, path string) string {
	path = strings.TrimSpace(path)
	if path == "" {
		return ""
	}
	if filepath.IsAbs(path) {
		absInput, err := filepath.Abs(inputRoot)
		if err == nil {
			if rel, ok := safeRel(absInput, path); ok {
				return filepath.ToSlash(rel)
			}
		}
		return filepath.Base(path)
	}
	cleaned := filepath.Clean(path)
	if cleaned == "." {
		return ""
	}
	if strings.HasPrefix(cleaned, ".."+string(filepath.Separator)) || cleaned == ".." {
		return filepath.Base(cleaned)
	}
	return filepath.ToSlash(strings.TrimPrefix(cleaned, "./"))
}

func safeRel(root string, path string) (string, bool) {
	absPath, err := filepath.Abs(path)
	if err != nil {
		return "", false
	}
	rel, err := filepath.Rel(root, absPath)
	if err != nil || rel == "." || rel == "" || filepath.IsAbs(rel) || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
		return "", false
	}
	return rel, true
}

func absMaybeRelative(root string, path string) (string, error) {
	if path == "" {
		return "", fmt.Errorf("empty path")
	}
	if !filepath.IsAbs(path) {
		path = filepath.Join(root, path)
	}
	return filepath.Abs(path)
}

func sameCleanPath(left string, right string) bool {
	absLeft, errLeft := filepath.Abs(left)
	absRight, errRight := filepath.Abs(right)
	if errLeft == nil {
		left = absLeft
	}
	if errRight == nil {
		right = absRight
	}
	return filepath.Clean(left) == filepath.Clean(right)
}

func gitRevParse(root string, arg string) (string, error) {
	cmd := exec.Command("git", "rev-parse", arg)
	cmd.Dir = root
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("git rev-parse %s: %w: %s", arg, err, strings.TrimSpace(stderr.String()))
	}
	return strings.TrimSpace(stdout.String()), nil
}
