package doctor

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/steveyegge/gastown/internal/beads"
	"github.com/steveyegge/gastown/internal/daemon"
)

// bdDoctorResult represents the JSON output from bd doctor --json.
type bdDoctorResult struct {
	Checks []bdDoctorCheck `json:"checks"`
}

// bdDoctorCheck represents a single check result from bd doctor.
type bdDoctorCheck struct {
	Name    string `json:"name"`
	Status  string `json:"status"`
	Message string `json:"message"`
	Detail  string `json:"detail,omitempty"`
	Fix     string `json:"fix,omitempty"`
}

// RepoFingerprintCheck verifies that beads databases have valid repository fingerprints.
// A missing or mismatched fingerprint can cause daemon startup failures and sync issues.
type RepoFingerprintCheck struct {
	FixableCheck
	needsMigration bool   // Cached during Run for use in Fix
	beadsDir       string // Beads directory that needs migration
}

// NewRepoFingerprintCheck creates a new repo fingerprint check.
func NewRepoFingerprintCheck() *RepoFingerprintCheck {
	return &RepoFingerprintCheck{
		FixableCheck: FixableCheck{
			BaseCheck: BaseCheck{
				CheckName:        "repo-fingerprint",
				CheckDescription: "Verify beads database has valid repository fingerprint",
			},
		},
	}
}

// Run checks if beads databases have valid repo fingerprints.
func (c *RepoFingerprintCheck) Run(ctx *CheckContext) *CheckResult {
	// Reset cached state
	c.needsMigration = false
	c.beadsDir = ""

	// Check town-level beads
	townBeadsDir := filepath.Join(ctx.TownRoot, ".beads")
	if _, err := os.Stat(townBeadsDir); err == nil {
		result := c.checkBeadsDir(filepath.Dir(townBeadsDir), "town")
		if result.Status != StatusOK {
			return result
		}
	}

	// Check rig-level beads if specified
	if ctx.RigName != "" {
		rigBeadsDir := beads.ResolveBeadsDir(ctx.RigPath())
		if _, err := os.Stat(rigBeadsDir); err == nil {
			result := c.checkBeadsDir(filepath.Dir(rigBeadsDir), "rig "+ctx.RigName)
			if result.Status != StatusOK {
				return result
			}
		}
	}

	return &CheckResult{
		Name:    c.Name(),
		Status:  StatusOK,
		Message: "Repository fingerprints verified",
	}
}

// checkBeadsDir checks a single beads directory for repo fingerprint using bd doctor.
func (c *RepoFingerprintCheck) checkBeadsDir(workDir, location string) *CheckResult {
	// Run bd doctor --json to get fingerprint status
	cmd := exec.Command("bd", "doctor", "--json")
	cmd.Dir = workDir
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	// bd doctor exits with non-zero if there are warnings, so ignore exit code
	_ = cmd.Run()

	// Parse JSON output
	var result bdDoctorResult
	if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
		// If we can't parse bd doctor output, skip this check
		return &CheckResult{
			Name:    c.Name(),
			Status:  StatusOK,
			Message: fmt.Sprintf("Skipped %s (bd doctor unavailable)", location),
		}
	}

	// Find the Repo Fingerprint check
	for _, check := range result.Checks {
		if check.Name == "Repo Fingerprint" {
			switch check.Status {
			case "ok":
				return &CheckResult{
					Name:    c.Name(),
					Status:  StatusOK,
					Message: fmt.Sprintf("Fingerprint verified in %s (%s)", location, check.Message),
				}
			case "warning":
				c.needsMigration = true
				c.beadsDir = filepath.Join(workDir, ".beads")
				return &CheckResult{
					Name:    c.Name(),
					Status:  StatusWarning,
					Message: fmt.Sprintf("Fingerprint issue in %s: %s", location, check.Message),
					Details: func() []string {
						if check.Detail != "" {
							return []string{check.Detail}
						}
						return nil
					}(),
					FixHint: "Run 'gt doctor --fix' or 'bd migrate --update-repo-id'",
				}
			case "error":
				c.needsMigration = true
				c.beadsDir = filepath.Join(workDir, ".beads")
				return &CheckResult{
					Name:    c.Name(),
					Status:  StatusError,
					Message: fmt.Sprintf("Fingerprint error in %s: %s", location, check.Message),
					Details: func() []string {
						if check.Detail != "" {
							return []string{check.Detail}
						}
						return nil
					}(),
					FixHint: "Run 'gt doctor --fix' or 'bd migrate --update-repo-id'",
				}
			}
		}
	}

	// Fingerprint check not found in bd doctor output - skip
	return &CheckResult{
		Name:    c.Name(),
		Status:  StatusOK,
		Message: fmt.Sprintf("Fingerprint check not applicable for %s", location),
	}
}

// Fix runs bd migrate --update-repo-id and restarts the daemon.
func (c *RepoFingerprintCheck) Fix(ctx *CheckContext) error {
	if !c.needsMigration || c.beadsDir == "" {
		return nil
	}

	// Run bd migrate --update-repo-id
	cmd := exec.Command("bd", "migrate", "--update-repo-id")
	cmd.Dir = filepath.Dir(c.beadsDir) // Parent of .beads directory
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("bd migrate --update-repo-id failed: %v: %s", err, stderr.String())
	}

	// Restart daemon if running
	running, _, err := daemon.IsRunning(ctx.TownRoot)
	if err == nil && running {
		// Stop daemon
		stopCmd := exec.Command("gt", "daemon", "stop")
		stopCmd.Dir = ctx.TownRoot
		_ = stopCmd.Run() // Ignore errors

		// Wait a moment
		time.Sleep(500 * time.Millisecond)

		// Start daemon
		startCmd := exec.Command("gt", "daemon", "run")
		startCmd.Dir = ctx.TownRoot
		startCmd.Stdin = nil
		startCmd.Stdout = nil
		startCmd.Stderr = nil
		if err := startCmd.Start(); err != nil {
			return fmt.Errorf("failed to restart daemon: %w", err)
		}

		// Wait for daemon to initialize
		time.Sleep(300 * time.Millisecond)
	}

	return nil
}
