package cmd

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

	"github.com/spf13/cobra"
	"github.com/steveyegge/gastown/internal/formula"
	"github.com/steveyegge/gastown/internal/runtime"
	"github.com/steveyegge/gastown/internal/style"
	"github.com/steveyegge/gastown/internal/workspace"
)

// Synthesis command flags
var (
	synthesisRig     string
	synthesisDryRun  bool
	synthesisForce   bool
	synthesisReviewID string
)

var synthesisCmd = &cobra.Command{
	Use:     "synthesis",
	Aliases: []string{"synth"},
	GroupID: GroupWork,
	Short:   "Manage convoy synthesis steps",
	RunE:    requireSubcommand,
	Long: `Manage synthesis steps for convoy formulas.

Synthesis is the final step in a convoy workflow that combines outputs
from all parallel legs into a unified deliverable.

Commands:
  start     Start synthesis for a convoy (checks all legs complete)
  status    Show synthesis readiness and leg outputs
  close     Close convoy after synthesis complete

Examples:
  gt synthesis status hq-cv-abc     # Check if ready for synthesis
  gt synthesis start hq-cv-abc      # Start synthesis step
  gt synthesis close hq-cv-abc      # Close convoy after synthesis`,
}

var synthesisStartCmd = &cobra.Command{
	Use:   "start <convoy-id>",
	Short: "Start synthesis for a convoy",
	Long: `Start the synthesis step for a convoy.

This command:
  1. Verifies all legs are complete
  2. Collects outputs from all legs
  3. Creates a synthesis bead with combined context
  4. Slings the synthesis to a polecat

Options:
  --rig=NAME      Target rig for synthesis polecat (default: current)
  --review-id=ID  Override review ID for output paths
  --force         Start synthesis even if some legs incomplete
  --dry-run       Show what would happen without executing`,
	Args: cobra.ExactArgs(1),
	RunE: runSynthesisStart,
}

var synthesisStatusCmd = &cobra.Command{
	Use:   "status <convoy-id>",
	Short: "Show synthesis readiness",
	Long: `Show whether a convoy is ready for synthesis.

Displays:
  - Convoy metadata
  - Leg completion status
  - Available leg outputs
  - Formula synthesis configuration`,
	Args: cobra.ExactArgs(1),
	RunE: runSynthesisStatus,
}

var synthesisCloseCmd = &cobra.Command{
	Use:   "close <convoy-id>",
	Short: "Close convoy after synthesis",
	Long: `Close a convoy after synthesis is complete.

This marks the convoy as complete and triggers any configured notifications.`,
	Args: cobra.ExactArgs(1),
	RunE: runSynthesisClose,
}

func init() {
	// Start flags
	synthesisStartCmd.Flags().StringVar(&synthesisRig, "rig", "", "Target rig for synthesis polecat")
	synthesisStartCmd.Flags().BoolVar(&synthesisDryRun, "dry-run", false, "Preview execution")
	synthesisStartCmd.Flags().BoolVar(&synthesisForce, "force", false, "Start even if legs incomplete")
	synthesisStartCmd.Flags().StringVar(&synthesisReviewID, "review-id", "", "Override review ID")

	// Add subcommands
	synthesisCmd.AddCommand(synthesisStartCmd)
	synthesisCmd.AddCommand(synthesisStatusCmd)
	synthesisCmd.AddCommand(synthesisCloseCmd)

	rootCmd.AddCommand(synthesisCmd)
}

// LegOutput represents collected output from a convoy leg.
type LegOutput struct {
	LegID    string `json:"leg_id"`
	Title    string `json:"title"`
	Status   string `json:"status"`
	FilePath string `json:"file_path,omitempty"`
	Content  string `json:"content,omitempty"`
	HasFile  bool   `json:"has_file"`
}

// ConvoyMeta holds metadata about a convoy including its formula.
type ConvoyMeta struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Status      string   `json:"status"`
	Formula     string   `json:"formula,omitempty"`     // Formula name
	FormulaPath string   `json:"formula_path,omitempty"` // Path to formula file
	ReviewID    string   `json:"review_id,omitempty"`    // Review ID for output paths
	LegIssues   []string `json:"leg_issues,omitempty"`   // Tracked leg issue IDs
}

// runSynthesisStart implements gt synthesis start.
func runSynthesisStart(cmd *cobra.Command, args []string) error {
	convoyID := args[0]

	// Get convoy metadata
	meta, err := getConvoyMeta(convoyID)
	if err != nil {
		return fmt.Errorf("getting convoy metadata: %w", err)
	}

	fmt.Printf("%s Checking synthesis readiness for %s...\n", style.Bold.Render("🔬"), convoyID)

	// Load formula if specified
	var f *formula.Formula
	if meta.FormulaPath != "" {
		f, err = formula.ParseFile(meta.FormulaPath)
		if err != nil {
			return fmt.Errorf("loading formula: %w", err)
		}
	} else if meta.Formula != "" {
		// Try to find formula by name
		formulaPath, findErr := findFormula(meta.Formula)
		if findErr == nil {
			f, err = formula.ParseFile(formulaPath)
			if err != nil {
				return fmt.Errorf("loading formula: %w", err)
			}
		}
	}

	// Check leg completion status
	legOutputs, allComplete, err := collectLegOutputs(meta, f)
	if err != nil {
		return fmt.Errorf("collecting leg outputs: %w", err)
	}

	// Report status
	completedCount := 0
	for _, leg := range legOutputs {
		if leg.Status == "closed" {
			completedCount++
		}
	}
	fmt.Printf("  Legs: %d/%d complete\n", completedCount, len(legOutputs))

	if !allComplete && !synthesisForce {
		fmt.Printf("\n%s Not all legs complete. Use --force to proceed anyway.\n",
			style.Warning.Render("⚠"))
		fmt.Printf("\nIncomplete legs:\n")
		for _, leg := range legOutputs {
			if leg.Status != "closed" {
				fmt.Printf("  ○ %s: %s [%s]\n", leg.LegID, leg.Title, leg.Status)
			}
		}
		return nil
	}

	// Determine review ID
	reviewID := synthesisReviewID
	if reviewID == "" {
		reviewID = meta.ReviewID
	}
	if reviewID == "" {
		// Extract from convoy ID
		reviewID = strings.TrimPrefix(convoyID, "hq-cv-")
	}

	// Determine target rig
	targetRig := synthesisRig
	if targetRig == "" {
		townRoot, err := workspace.FindFromCwdOrError()
		if err == nil {
			rigName, _, rigErr := findCurrentRig(townRoot)
			if rigErr == nil && rigName != "" {
				targetRig = rigName
			}
		}
		if targetRig == "" {
			targetRig = "gastown"
		}
	}

	if synthesisDryRun {
		fmt.Printf("\n%s Would start synthesis:\n", style.Dim.Render("[dry-run]"))
		fmt.Printf("  Convoy:    %s\n", convoyID)
		fmt.Printf("  Review ID: %s\n", reviewID)
		fmt.Printf("  Target:    %s\n", targetRig)
		fmt.Printf("  Legs:      %d outputs collected\n", len(legOutputs))
		if f != nil && f.Synthesis != nil {
			fmt.Printf("  Synthesis: %s\n", f.Synthesis.Title)
		}
		return nil
	}

	// Create synthesis bead
	synthesisID, err := createSynthesisBead(convoyID, meta, f, legOutputs, reviewID)
	if err != nil {
		return fmt.Errorf("creating synthesis bead: %w", err)
	}
	fmt.Printf("%s Created synthesis bead: %s\n", style.Bold.Render("✓"), synthesisID)

	// Sling to target rig
	fmt.Printf("  Slinging to %s...\n", targetRig)
	if err := slingSynthesis(synthesisID, targetRig); err != nil {
		return fmt.Errorf("slinging synthesis: %w", err)
	}

	fmt.Printf("%s Synthesis started\n", style.Bold.Render("✓"))
	fmt.Printf("  Monitor: gt convoy status %s\n", convoyID)

	return nil
}

// runSynthesisStatus implements gt synthesis status.
func runSynthesisStatus(cmd *cobra.Command, args []string) error {
	convoyID := args[0]

	meta, err := getConvoyMeta(convoyID)
	if err != nil {
		return fmt.Errorf("getting convoy metadata: %w", err)
	}

	// Load formula if available
	var f *formula.Formula
	if meta.FormulaPath != "" {
		f, _ = formula.ParseFile(meta.FormulaPath)
	} else if meta.Formula != "" {
		if path, err := findFormula(meta.Formula); err == nil {
			f, _ = formula.ParseFile(path)
		}
	}

	// Collect leg outputs
	legOutputs, allComplete, err := collectLegOutputs(meta, f)
	if err != nil {
		return fmt.Errorf("collecting leg outputs: %w", err)
	}

	// Display status
	fmt.Printf("🚚 %s %s\n\n", style.Bold.Render(convoyID+":"), meta.Title)
	fmt.Printf("  Status: %s\n", formatConvoyStatus(meta.Status))

	if meta.Formula != "" {
		fmt.Printf("  Formula: %s\n", meta.Formula)
	}

	fmt.Printf("\n  %s\n", style.Bold.Render("Legs:"))
	for _, leg := range legOutputs {
		status := "○"
		if leg.Status == "closed" {
			status = "✓"
		}
		fileStatus := ""
		if leg.HasFile {
			fileStatus = style.Dim.Render(" (output: ✓)")
		}
		fmt.Printf("    %s %s: %s [%s]%s\n", status, leg.LegID, leg.Title, leg.Status, fileStatus)
	}

	// Synthesis readiness
	fmt.Printf("\n  %s\n", style.Bold.Render("Synthesis:"))
	if allComplete {
		fmt.Printf("    %s Ready - all legs complete\n", style.Success.Render("✓"))
		fmt.Printf("    Run: gt synthesis start %s\n", convoyID)
	} else {
		completedCount := 0
		for _, leg := range legOutputs {
			if leg.Status == "closed" {
				completedCount++
			}
		}
		fmt.Printf("    %s Waiting - %d/%d legs complete\n",
			style.Warning.Render("○"), completedCount, len(legOutputs))
	}

	if f != nil && f.Synthesis != nil {
		fmt.Printf("\n  %s\n", style.Bold.Render("Synthesis Config:"))
		fmt.Printf("    Title: %s\n", f.Synthesis.Title)
		if f.Output != nil && f.Output.Synthesis != "" {
			fmt.Printf("    Output: %s\n", f.Output.Synthesis)
		}
	}

	return nil
}

// runSynthesisClose implements gt synthesis close.
func runSynthesisClose(cmd *cobra.Command, args []string) error {
	convoyID := args[0]

	townBeads, err := getTownBeadsDir()
	if err != nil {
		return err
	}

	// Close the convoy
	closeArgs := []string{"close", convoyID, "--reason=synthesis complete"}
	if sessionID := runtime.SessionIDFromEnv(); sessionID != "" {
		closeArgs = append(closeArgs, "--session="+sessionID)
	}
	closeCmd := exec.Command("bd", closeArgs...)
	closeCmd.Dir = townBeads
	closeCmd.Stderr = os.Stderr

	if err := closeCmd.Run(); err != nil {
		return fmt.Errorf("closing convoy: %w", err)
	}

	fmt.Printf("%s Convoy closed: %s\n", style.Bold.Render("✓"), convoyID)

	// TODO: Trigger notification if configured
	// Parse description for "Notify: <address>" and send mail

	return nil
}

// getConvoyMeta retrieves convoy metadata from beads.
func getConvoyMeta(convoyID string) (*ConvoyMeta, error) {
	townBeads, err := getTownBeadsDir()
	if err != nil {
		return nil, err
	}

	showCmd := exec.Command("bd", "show", convoyID, "--json")
	showCmd.Dir = townBeads
	var stdout bytes.Buffer
	showCmd.Stdout = &stdout

	if err := showCmd.Run(); err != nil {
		return nil, fmt.Errorf("convoy '%s' not found", convoyID)
	}

	var convoys []struct {
		ID          string `json:"id"`
		Title       string `json:"title"`
		Status      string `json:"status"`
		Description string `json:"description"`
		Type        string `json:"issue_type"`
	}
	if err := json.Unmarshal(stdout.Bytes(), &convoys); err != nil {
		return nil, fmt.Errorf("parsing convoy data: %w", err)
	}

	if len(convoys) == 0 || convoys[0].Type != "convoy" {
		return nil, fmt.Errorf("'%s' is not a convoy", convoyID)
	}

	convoy := convoys[0]

	// Parse formula and review ID from description
	meta := &ConvoyMeta{
		ID:     convoy.ID,
		Title:  convoy.Title,
		Status: convoy.Status,
	}

	// Look for structured fields in description
	for _, line := range strings.Split(convoy.Description, "\n") {
		line = strings.TrimSpace(line)
		if colonIdx := strings.Index(line, ":"); colonIdx != -1 {
			key := strings.ToLower(strings.TrimSpace(line[:colonIdx]))
			value := strings.TrimSpace(line[colonIdx+1:])
			switch key {
			case "formula":
				meta.Formula = value
			case "formula_path", "formula-path":
				meta.FormulaPath = value
			case "review_id", "review-id":
				meta.ReviewID = value
			}
		}
	}

	// Get tracked leg issues
	tracked := getTrackedIssues(townBeads, convoyID)
	for _, t := range tracked {
		meta.LegIssues = append(meta.LegIssues, t.ID)
	}

	return meta, nil
}

// collectLegOutputs gathers outputs from all convoy legs.
func collectLegOutputs(meta *ConvoyMeta, f *formula.Formula) ([]LegOutput, bool, error) { //nolint:unparam // error return kept for future use
	var outputs []LegOutput
	allComplete := true

	// If we have tracked issues, use those as legs
	if len(meta.LegIssues) > 0 {
		for _, issueID := range meta.LegIssues {
			details := getIssueDetails(issueID)
			output := LegOutput{
				LegID: issueID,
				Title: "(unknown)",
			}
			if details != nil {
				output.Title = details.Title
				output.Status = details.Status
			}
			if output.Status != "closed" {
				allComplete = false
			}
			outputs = append(outputs, output)
		}
	}

	// If we have a formula, also try to find output files
	if f != nil && f.Output != nil && meta.ReviewID != "" {
		for _, leg := range f.Legs {
			// Expand output path template
			outputPath := expandOutputPath(f.Output.Directory, f.Output.LegPattern,
				meta.ReviewID, leg.ID)

			// Check if file exists and read content
			if content, err := os.ReadFile(outputPath); err == nil {
				// Find or create leg output entry
				found := false
				for i := range outputs {
					if outputs[i].LegID == leg.ID {
						outputs[i].FilePath = outputPath
						outputs[i].Content = string(content)
						outputs[i].HasFile = true
						found = true
						break
					}
				}
				if !found {
					outputs = append(outputs, LegOutput{
						LegID:    leg.ID,
						Title:    leg.Title,
						Status:   "closed", // If file exists, assume complete
						FilePath: outputPath,
						Content:  string(content),
						HasFile:  true,
					})
				}
			}
		}
	}

	return outputs, allComplete, nil
}

// expandOutputPath expands template variables in output paths.
// Supports: {{review_id}}, {{leg.id}}
func expandOutputPath(directory, pattern, reviewID, legID string) string {
	// Expand directory
	dir := strings.ReplaceAll(directory, "{{review_id}}", reviewID)

	// Expand pattern
	file := strings.ReplaceAll(pattern, "{{leg.id}}", legID)

	return filepath.Join(dir, file)
}

// createSynthesisBead creates a bead for the synthesis step.
func createSynthesisBead(convoyID string, meta *ConvoyMeta, f *formula.Formula,
	legOutputs []LegOutput, reviewID string) (string, error) {

	// Build synthesis title
	title := "Synthesis: " + meta.Title
	if f != nil && f.Synthesis != nil && f.Synthesis.Title != "" {
		title = f.Synthesis.Title + ": " + meta.Title
	}

	// Build synthesis description with leg outputs
	var desc strings.Builder
	desc.WriteString(fmt.Sprintf("convoy: %s\n", convoyID))
	desc.WriteString(fmt.Sprintf("review_id: %s\n", reviewID))
	desc.WriteString("\n")

	// Add synthesis instructions from formula
	if f != nil && f.Synthesis != nil && f.Synthesis.Description != "" {
		desc.WriteString("## Instructions\n\n")
		desc.WriteString(f.Synthesis.Description)
		desc.WriteString("\n\n")
	}

	// Add collected leg outputs
	desc.WriteString("## Leg Outputs\n\n")
	for _, leg := range legOutputs {
		desc.WriteString(fmt.Sprintf("### %s: %s\n\n", leg.LegID, leg.Title))
		if leg.Content != "" {
			desc.WriteString(leg.Content)
			desc.WriteString("\n\n")
		} else if leg.FilePath != "" {
			desc.WriteString(fmt.Sprintf("Output file: %s\n\n", leg.FilePath))
		} else {
			desc.WriteString("(no output available)\n\n")
		}
	}

	// Add output path if configured
	if f != nil && f.Output != nil && f.Output.Synthesis != "" {
		outputPath := strings.ReplaceAll(f.Output.Directory, "{{review_id}}", reviewID)
		outputPath = filepath.Join(outputPath, f.Output.Synthesis)
		desc.WriteString(fmt.Sprintf("\n## Output\n\nWrite synthesis to: %s\n", outputPath))
	}

	// Create the bead
	createArgs := []string{
		"create",
		"--type=task",
		"--title=" + title,
		"--description=" + desc.String(),
		"--json",
	}

	townBeads, err := getTownBeadsDir()
	if err != nil {
		return "", err
	}

	createCmd := exec.Command("bd", createArgs...)
	createCmd.Dir = townBeads
	var stdout bytes.Buffer
	createCmd.Stdout = &stdout
	createCmd.Stderr = os.Stderr

	if err := createCmd.Run(); err != nil {
		return "", fmt.Errorf("creating synthesis bead: %w", err)
	}

	// Parse created bead ID
	var result struct {
		ID string `json:"id"`
	}
	if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
		// Try to extract ID from non-JSON output
		out := strings.TrimSpace(stdout.String())
		if strings.HasPrefix(out, "hq-") || strings.HasPrefix(out, "gt-") {
			return out, nil
		}
		return "", fmt.Errorf("parsing created bead: %w", err)
	}

	// Add tracking relation: convoy tracks synthesis
	depArgs := []string{"dep", "add", convoyID, result.ID, "--type=tracks"}
	depCmd := exec.Command("bd", depArgs...)
	depCmd.Dir = townBeads
	_ = depCmd.Run() // Non-fatal if this fails

	return result.ID, nil
}

// slingSynthesis slings the synthesis bead to a rig.
func slingSynthesis(beadID, targetRig string) error {
	slingArgs := []string{"sling", beadID, targetRig}
	slingCmd := exec.Command("gt", slingArgs...)
	slingCmd.Stdout = os.Stdout
	slingCmd.Stderr = os.Stderr

	return slingCmd.Run()
}

// findFormula searches for a formula file by name.
func findFormula(name string) (string, error) {
	// Search paths
	searchPaths := []string{
		".beads/formulas",
	}

	// Add home directory formulas
	if home, err := os.UserHomeDir(); err == nil {
		searchPaths = append(searchPaths, filepath.Join(home, ".beads", "formulas"))
	}

	// Add GT_ROOT formulas if set
	if gtRoot := os.Getenv("GT_ROOT"); gtRoot != "" {
		searchPaths = append(searchPaths, filepath.Join(gtRoot, ".beads", "formulas"))
	}

	// Try each search path
	for _, searchPath := range searchPaths {
		// Try with .formula.toml extension
		path := filepath.Join(searchPath, name+".formula.toml")
		if _, err := os.Stat(path); err == nil {
			return path, nil
		}

		// Try with .formula.json extension
		path = filepath.Join(searchPath, name+".formula.json")
		if _, err := os.Stat(path); err == nil {
			return path, nil
		}
	}

	return "", fmt.Errorf("formula '%s' not found", name)
}

// CheckSynthesisReady checks if a convoy is ready for synthesis.
// Returns true if all tracked legs are complete.
func CheckSynthesisReady(convoyID string) (bool, error) {
	meta, err := getConvoyMeta(convoyID)
	if err != nil {
		return false, err
	}

	_, allComplete, err := collectLegOutputs(meta, nil)
	return allComplete, err
}

// TriggerSynthesisIfReady checks convoy status and starts synthesis if ready.
// This can be called by the witness when a leg completes.
func TriggerSynthesisIfReady(convoyID, targetRig string) error {
	ready, err := CheckSynthesisReady(convoyID)
	if err != nil {
		return err
	}

	if !ready {
		return nil // Not ready yet
	}

	// Synthesis is ready - start it
	fmt.Printf("%s All legs complete, starting synthesis...\n", style.Bold.Render("🔬"))

	meta, err := getConvoyMeta(convoyID)
	if err != nil {
		return err
	}

	// Load formula if available
	var f *formula.Formula
	if meta.FormulaPath != "" {
		f, _ = formula.ParseFile(meta.FormulaPath)
	} else if meta.Formula != "" {
		if path, err := findFormula(meta.Formula); err == nil {
			f, _ = formula.ParseFile(path)
		}
	}

	legOutputs, _, _ := collectLegOutputs(meta, f)
	reviewID := meta.ReviewID
	if reviewID == "" {
		reviewID = strings.TrimPrefix(convoyID, "hq-cv-")
	}

	synthesisID, err := createSynthesisBead(convoyID, meta, f, legOutputs, reviewID)
	if err != nil {
		return fmt.Errorf("creating synthesis bead: %w", err)
	}

	if err := slingSynthesis(synthesisID, targetRig); err != nil {
		return fmt.Errorf("slinging synthesis: %w", err)
	}

	return nil
}
