package doctor

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// CrewStateCheck validates crew worker state.json files for completeness.
// Empty or incomplete state.json files cause "can't find pane/session" errors.
type CrewStateCheck struct {
	FixableCheck
	invalidCrews []invalidCrew // Cached during Run for use in Fix
}

type invalidCrew struct {
	path      string
	stateFile string
	rigName   string
	crewName  string
	issue     string
}

// NewCrewStateCheck creates a new crew state check.
func NewCrewStateCheck() *CrewStateCheck {
	return &CrewStateCheck{
		FixableCheck: FixableCheck{
			BaseCheck: BaseCheck{
				CheckName:        "crew-state",
				CheckDescription: "Validate crew worker state.json files",
			},
		},
	}
}

// Run checks all crew state.json files for completeness.
func (c *CrewStateCheck) Run(ctx *CheckContext) *CheckResult {
	c.invalidCrews = nil

	crewDirs := c.findAllCrewDirs(ctx.TownRoot)
	if len(crewDirs) == 0 {
		return &CheckResult{
			Name:    c.Name(),
			Status:  StatusOK,
			Message: "No crew workspaces found",
		}
	}

	var validCount int
	var details []string

	for _, cd := range crewDirs {
		stateFile := filepath.Join(cd.path, "state.json")

		// Check if state.json exists
		data, err := os.ReadFile(stateFile)
		if err != nil {
			if os.IsNotExist(err) {
				// Missing state file is OK - code will use defaults
				validCount++
				continue
			}
			// Other errors are problems
			issue := fmt.Sprintf("cannot read state.json: %v", err)
			c.invalidCrews = append(c.invalidCrews, invalidCrew{
				path:      cd.path,
				stateFile: stateFile,
				rigName:   cd.rigName,
				crewName:  cd.crewName,
				issue:     issue,
			})
			details = append(details, fmt.Sprintf("%s/%s: %s", cd.rigName, cd.crewName, issue))
			continue
		}

		// Parse state.json
		var state struct {
			Name      string `json:"name"`
			Rig       string `json:"rig"`
			ClonePath string `json:"clone_path"`
		}
		if err := json.Unmarshal(data, &state); err != nil {
			issue := "invalid JSON in state.json"
			c.invalidCrews = append(c.invalidCrews, invalidCrew{
				path:      cd.path,
				stateFile: stateFile,
				rigName:   cd.rigName,
				crewName:  cd.crewName,
				issue:     issue,
			})
			details = append(details, fmt.Sprintf("%s/%s: %s", cd.rigName, cd.crewName, issue))
			continue
		}

		// Check for empty/incomplete state
		var issues []string
		if state.Name == "" {
			issues = append(issues, "missing name")
		}
		if state.Rig == "" {
			issues = append(issues, "missing rig")
		}
		if state.ClonePath == "" {
			issues = append(issues, "missing clone_path")
		}

		if len(issues) > 0 {
			issue := strings.Join(issues, ", ")
			c.invalidCrews = append(c.invalidCrews, invalidCrew{
				path:      cd.path,
				stateFile: stateFile,
				rigName:   cd.rigName,
				crewName:  cd.crewName,
				issue:     issue,
			})
			details = append(details, fmt.Sprintf("%s/%s: %s", cd.rigName, cd.crewName, issue))
		} else {
			validCount++
		}
	}

	if len(c.invalidCrews) == 0 {
		return &CheckResult{
			Name:    c.Name(),
			Status:  StatusOK,
			Message: fmt.Sprintf("All %d crew state files valid", validCount),
		}
	}

	return &CheckResult{
		Name:    c.Name(),
		Status:  StatusWarning,
		Message: fmt.Sprintf("%d crew workspace(s) with invalid state.json", len(c.invalidCrews)),
		Details: details,
		FixHint: "Run 'gt doctor --fix' to regenerate state files",
	}
}

// Fix regenerates invalid state.json files with correct values.
func (c *CrewStateCheck) Fix(ctx *CheckContext) error {
	if len(c.invalidCrews) == 0 {
		return nil
	}

	var lastErr error
	for _, ic := range c.invalidCrews {
		state := map[string]interface{}{
			"name":       ic.crewName,
			"rig":        ic.rigName,
			"clone_path": ic.path,
			"branch":     "main",
			"created_at": time.Now().Format(time.RFC3339),
			"updated_at": time.Now().Format(time.RFC3339),
		}

		data, err := json.MarshalIndent(state, "", "  ")
		if err != nil {
			lastErr = fmt.Errorf("%s/%s: %w", ic.rigName, ic.crewName, err)
			continue
		}

		if err := os.WriteFile(ic.stateFile, data, 0644); err != nil {
			lastErr = fmt.Errorf("%s/%s: %w", ic.rigName, ic.crewName, err)
			continue
		}
	}

	return lastErr
}

type crewDir struct {
	path     string
	rigName  string
	crewName string
}

// findAllCrewDirs finds all crew directories in the workspace.
func (c *CrewStateCheck) findAllCrewDirs(townRoot string) []crewDir {
	var dirs []crewDir

	entries, err := os.ReadDir(townRoot)
	if err != nil {
		return dirs
	}

	for _, entry := range entries {
		if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") || entry.Name() == "mayor" {
			continue
		}

		rigName := entry.Name()
		crewPath := filepath.Join(townRoot, rigName, "crew")

		crewEntries, err := os.ReadDir(crewPath)
		if err != nil {
			continue
		}

		for _, crew := range crewEntries {
			if !crew.IsDir() || strings.HasPrefix(crew.Name(), ".") {
				continue
			}
			dirs = append(dirs, crewDir{
				path:     filepath.Join(crewPath, crew.Name()),
				rigName:  rigName,
				crewName: crew.Name(),
			})
		}
	}

	return dirs
}
