package infra

import (
	"bufio"
	"bytes"
	"context"
	"database/sql"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

// ArtefactCheck holds the health report for a single artefact (app or analysis).
type ArtefactCheck struct {
	ID      string   // registry id, e.g. "kanban_go_tools"
	Type    string   // "app" or "analysis"
	DirPath string   // dir_path as stored in registry.db
	Issues  []string // human-readable problems; empty means healthy
	OK      bool     // true when len(Issues) == 0
}

// ArtefactDoctor audits every app and analysis registered in registry.db.
// It checks disk presence, git initialisation, manifest parseability, venv
// health (analyses only) and upstream branch configuration.
// The function is read-only: it never modifies any file or database.
// Returns an error only if registry.db cannot be opened.
func ArtefactDoctor(registryRoot string) ([]ArtefactCheck, error) {
	dbPath := filepath.Join(registryRoot, "registry.db")
	dsn := fmt.Sprintf("file:%s?mode=ro&_foreign_keys=on", dbPath)
	db, err := sql.Open("sqlite3", dsn)
	if err != nil {
		return nil, fmt.Errorf("artefact_doctor: open db: %w", err)
	}
	defer db.Close()
	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("artefact_doctor: ping db: %w", err)
	}

	var checks []ArtefactCheck

	rows, err := db.Query("SELECT id, dir_path FROM apps")
	if err != nil {
		return nil, fmt.Errorf("artefact_doctor: query apps: %w", err)
	}
	defer rows.Close()
	for rows.Next() {
		var id, dirPath string
		if err := rows.Scan(&id, &dirPath); err != nil {
			continue
		}
		checks = append(checks, checkArtefact(id, "app", dirPath, registryRoot))
	}

	rows2, err := db.Query("SELECT id, dir_path FROM analysis")
	if err != nil {
		return nil, fmt.Errorf("artefact_doctor: query analysis: %w", err)
	}
	defer rows2.Close()
	for rows2.Next() {
		var id, dirPath string
		if err := rows2.Scan(&id, &dirPath); err != nil {
			continue
		}
		checks = append(checks, checkArtefact(id, "analysis", dirPath, registryRoot))
	}

	return checks, nil
}

func checkArtefact(id, kind, dirPath, registryRoot string) ArtefactCheck {
	c := ArtefactCheck{ID: id, Type: kind, DirPath: dirPath}
	absDir := filepath.Join(registryRoot, dirPath)

	// 1. Directory exists
	if _, err := os.Stat(absDir); os.IsNotExist(err) {
		c.Issues = append(c.Issues, "directory_missing")
		c.OK = len(c.Issues) == 0
		return c
	}

	// 2. .git present
	gitPath := filepath.Join(absDir, ".git")
	if _, err := os.Stat(gitPath); os.IsNotExist(err) {
		c.Issues = append(c.Issues, "git_not_initialized")
	}

	// 3. Manifest parseable
	var mdFile string
	switch kind {
	case "app":
		mdFile = "app.md"
	case "analysis":
		mdFile = "analysis.md"
	}
	mdPath := filepath.Join(absDir, mdFile)
	if _, err := os.Stat(mdPath); os.IsNotExist(err) {
		c.Issues = append(c.Issues, mdFile[:len(mdFile)-3]+"_md_missing")
	} else if !frontmatterHasName(mdPath) {
		c.Issues = append(c.Issues, mdFile[:len(mdFile)-3]+"_md_invalid_frontmatter")
	}

	// 4. Analysis venv check
	if kind == "analysis" {
		python3 := filepath.Join(absDir, ".venv", "bin", "python3")
		fi, err := os.Lstat(python3)
		if os.IsNotExist(err) {
			c.Issues = append(c.Issues, "venv_missing")
		} else if err == nil {
			if fi.Mode()&os.ModeSymlink != 0 {
				target, lerr := os.Readlink(python3)
				if lerr == nil {
					if !filepath.IsAbs(target) {
						target = filepath.Join(filepath.Dir(python3), target)
					}
					if _, serr := os.Stat(target); os.IsNotExist(serr) {
						c.Issues = append(c.Issues, "venv_broken_path")
					}
				}
			} else if fi.Mode().Perm()&0o111 == 0 {
				c.Issues = append(c.Issues, "venv_broken_path")
			}
		}
	}

	// 5. Upstream branch (only if .git exists)
	if _, err := os.Stat(gitPath); err == nil {
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()
		cmd := exec.CommandContext(ctx, "git", "-C", absDir, "rev-parse", "--abbrev-ref", "@{u}")
		var out bytes.Buffer
		cmd.Stdout = &out
		cmd.Stderr = &out
		if err := cmd.Run(); err != nil {
			c.Issues = append(c.Issues, "no_upstream_branch")
		}
	}

	c.OK = len(c.Issues) == 0
	return c
}

// frontmatterHasName returns true if the YAML frontmatter inside the file
// contains a line starting with "name:".
func frontmatterHasName(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	inFrontmatter := false
	for scanner.Scan() {
		line := scanner.Text()
		if line == "---" {
			if !inFrontmatter {
				inFrontmatter = true
				continue
			}
			break // closing ---
		}
		if inFrontmatter && strings.HasPrefix(line, "name:") {
			return true
		}
	}
	return false
}
