// Package tui is the impure shell layer for the TUI.
// It converts pure Intent values into real I/O via tea.Cmd.
package tui

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"time"

	tea "github.com/charmbracelet/bubbletea"

	puretui "github.com/enmanuel/agents/pkg/tui"
	"github.com/enmanuel/agents/shell/process"
)

// Adapter bridges pure Intents with the process Manager.
type Adapter struct {
	mgr *process.Manager
}

// NewAdapter creates an Adapter with the given Manager.
func NewAdapter(mgr *process.Manager) *Adapter {
	return &Adapter{mgr: mgr}
}

// RunIntent converts a pure Intent into a bubbletea Cmd that performs I/O.
func (a *Adapter) RunIntent(intent puretui.Intent) tea.Cmd {
	switch intent.Kind {

	case puretui.IntentLoadAgents:
		return a.loadAgents()

	case puretui.IntentEnableAgent:
		return a.enableAgent(intent.AgentID)

	case puretui.IntentDisableAgent:
		return a.disableAgent(intent.AgentID)

	case puretui.IntentReloadAgent:
		return a.reloadAgent(intent.AgentID)

	case puretui.IntentReloadAll:
		return a.reloadAll()

	case puretui.IntentRestartAgent:
		return a.restartAgent(intent.AgentID)

	case puretui.IntentLoadLogs:
		return a.loadLogs(intent.AgentID)

	case puretui.IntentStartLauncher:
		return a.startLauncher()

	case puretui.IntentStopLauncher:
		return a.stopLauncher()

	case puretui.IntentRestartLauncher:
		return a.restartLauncher()

	case puretui.IntentKillLauncher:
		return a.killLauncher()

	case puretui.IntentRebuildRestart:
		return a.rebuildRestart()

	case puretui.IntentRunTests:
		return a.runGoTests()

	case puretui.IntentRunGoTests:
		return a.runGoTests()

	case puretui.IntentRunE2ETests:
		return a.runE2ETests(false)

	case puretui.IntentRunE2EHeadTests:
		return a.runE2ETests(true)

	case puretui.IntentRunAllTests:
		return a.runAllTests()

	case puretui.IntentTick:
		return a.tick()

	case puretui.IntentQuit:
		return tea.Quit

	default:
		return nil
	}
}

func (a *Adapter) loadAgents() tea.Cmd {
	return func() tea.Msg {
		statuses, err := a.mgr.StatusAllUnified()
		if err != nil {
			return puretui.MsgAgentsLoaded{}
		}

		views := make([]puretui.AgentView, len(statuses))
		for i, s := range statuses {
			views[i] = puretui.AgentView{
				ID:      s.ID,
				Name:    s.Name,
				Version: s.Version,
				Desc:    s.Desc,
				Enabled: s.Enabled,
				Running: s.Running,
				PID:     s.PID,
			}
		}

		msg := puretui.MsgAgentsLoaded{
			Agents:          views,
			LauncherRunning: a.mgr.IsUnifiedRunning(),
			LauncherPID:     a.mgr.UnifiedPID(),
		}

		// Launcher stats
		if msg.LauncherRunning {
			if stats, err := a.mgr.UnifiedStats(); err == nil {
				msg.LauncherUptime = formatUptime(stats.UptimeSecs)
				msg.LauncherMemory = formatBytes(stats.MemRSSKB * 1024)
				msg.LauncherCPU = fmt.Sprintf("%.1f%%", stats.CPUPct)
				msg.LauncherLogSize = formatBytes(stats.LogBytes)
			}
		}

		return msg
	}
}

func (a *Adapter) enableAgent(id string) tea.Cmd {
	return func() tea.Msg {
		err := a.mgr.ToggleEnabled(id, true)
		return puretui.MsgActionDone{AgentID: id, Action: "Enable", Err: err}
	}
}

func (a *Adapter) disableAgent(id string) tea.Cmd {
	return func() tea.Msg {
		err := a.mgr.ToggleEnabled(id, false)
		return puretui.MsgActionDone{AgentID: id, Action: "Disable", Err: err}
	}
}

// reloadAgent hot-reloads a single agent via SIGHUP without stopping the launcher.
func (a *Adapter) reloadAgent(id string) tea.Cmd {
	return func() tea.Msg {
		pid := a.mgr.UnifiedPID()
		if pid <= 0 {
			return puretui.MsgActionDone{AgentID: id, Action: "Reload",
				Err: fmt.Errorf("el launcher no está corriendo")}
		}
		if id != "" {
			if err := os.WriteFile("run/reload.txt", []byte(id), 0o644); err != nil {
				return puretui.MsgActionDone{AgentID: id, Action: "Reload", Err: err}
			}
		}
		err := syscall.Kill(pid, syscall.SIGHUP)
		if err != nil {
			return puretui.MsgActionDone{AgentID: id, Action: "Reload", Err: err}
		}
		time.Sleep(1 * time.Second)
		return puretui.MsgActionDone{AgentID: id, Action: "Reload", Err: nil}
	}
}

// reloadAll hot-reloads all agents via SIGHUP (no reload.txt → reload all).
func (a *Adapter) reloadAll() tea.Cmd {
	return func() tea.Msg {
		pid := a.mgr.UnifiedPID()
		if pid <= 0 {
			return puretui.MsgServerActionDone{Action: "Reload All",
				Err: fmt.Errorf("el launcher no está corriendo")}
		}
		// Remove stale reload.txt so the launcher reloads all agents.
		_ = os.Remove("run/reload.txt")
		err := syscall.Kill(pid, syscall.SIGHUP)
		if err != nil {
			return puretui.MsgServerActionDone{Action: "Reload All", Err: err}
		}
		time.Sleep(1 * time.Second)
		return puretui.MsgServerActionDone{Action: "Reload All", Err: nil}
	}
}

// restartAgent stops and restarts the whole launcher (full restart, all agents).
func (a *Adapter) restartAgent(id string) tea.Cmd {
	return func() tea.Msg {
		_ = a.mgr.StopUnified()
		time.Sleep(500 * time.Millisecond)
		err := a.mgr.StartUnified()
		if err == nil {
			time.Sleep(500 * time.Millisecond)
		}
		return puretui.MsgActionDone{AgentID: id, Action: "Restart", Err: err}
	}
}

func (a *Adapter) startLauncher() tea.Cmd {
	return func() tea.Msg {
		err := a.mgr.StartUnified()
		if err == nil {
			time.Sleep(500 * time.Millisecond)
		}
		return puretui.MsgServerActionDone{Action: "Start", Err: err}
	}
}

func (a *Adapter) stopLauncher() tea.Cmd {
	return func() tea.Msg {
		err := a.mgr.StopUnified()
		return puretui.MsgServerActionDone{Action: "Stop", Err: err}
	}
}

func (a *Adapter) restartLauncher() tea.Cmd {
	return func() tea.Msg {
		_ = a.mgr.StopUnified()
		time.Sleep(500 * time.Millisecond)
		err := a.mgr.StartUnified()
		if err == nil {
			time.Sleep(500 * time.Millisecond)
		}
		return puretui.MsgServerActionDone{Action: "Restart", Err: err}
	}
}

func (a *Adapter) killLauncher() tea.Cmd {
	return func() tea.Msg {
		err := a.mgr.KillUnified()
		return puretui.MsgServerActionDone{Action: "Kill", Err: err}
	}
}

func (a *Adapter) rebuildRestart() tea.Cmd {
	return func() tea.Msg {
		wasRunning := a.mgr.IsUnifiedRunning()

		// Stop if running
		if wasRunning {
			_ = a.mgr.StopUnified()
			time.Sleep(500 * time.Millisecond)
		}

		// Build
		buildOut, buildErr := a.mgr.Build()
		if buildErr != nil {
			// Build failed — try to restart if was running
			if wasRunning {
				_ = a.mgr.StartUnified()
			}
			lines := strings.Split(strings.TrimSpace(buildOut), "\n")
			tail := buildOut
			if len(lines) > 5 {
				tail = strings.Join(lines[len(lines)-5:], "\n")
			}
			return puretui.MsgRebuildDone{BuildOK: false, BuildLog: tail}
		}

		// Restart launcher
		started := false
		var startErr error
		if wasRunning {
			startErr = a.mgr.StartUnified()
			if startErr == nil {
				started = true
				time.Sleep(500 * time.Millisecond)
			}
		}

		return puretui.MsgRebuildDone{
			BuildOK: true,
			Started: started,
			Err:     startErr,
		}
	}
}

func (a *Adapter) loadLogs(id string) tea.Cmd {
	return func() tea.Msg {
		var lines []string
		var err error
		if id == "" {
			// Launcher logs
			lines, err = a.mgr.UnifiedLogTail(100)
		} else {
			// Agent logs — in unified mode, all go to launcher log
			lines, err = a.mgr.UnifiedLogTail(100)
		}
		if err != nil {
			return puretui.MsgLogsLoaded{Lines: []string{"Error: " + err.Error()}}
		}
		return puretui.MsgLogsLoaded{Lines: lines}
	}
}

func (a *Adapter) runGoTests() tea.Cmd {
	return func() tea.Msg {
		goBin, err := exec.LookPath("go")
		if err != nil {
			goBin = "/usr/local/go/bin/go"
		}
		cmd := exec.Command(goBin, "test", "-tags", "goolm", "-count=1", "./...")
		cmd.Env = a.mgr.BuildEnv()
		out, err := cmd.CombinedOutput()

		output := strings.TrimSpace(string(out))
		if output == "" && err != nil {
			output = "Error: " + err.Error()
		}
		lines := strings.Split(output, "\n")
		return puretui.MsgTestsDone{Kind: puretui.TestKindGo, Passed: err == nil, Output: lines}
	}
}

func (a *Adapter) runE2ETests(headed bool) tea.Cmd {
	return func() tea.Msg {
		args := []string{"./dev-scripts/e2e/run.sh"}
		if headed {
			args = append(args, "--headed")
		}
		cmd := exec.Command("bash", args...)
		cmd.Env = a.mgr.BuildEnv()
		out, err := cmd.CombinedOutput()

		output := strings.TrimSpace(string(out))
		if output == "" && err != nil {
			output = "Error: " + err.Error()
		}
		lines := strings.Split(output, "\n")
		kind := puretui.TestKindE2E
		if headed {
			kind = puretui.TestKindE2EHead
		}
		return puretui.MsgTestsDone{Kind: kind, Passed: err == nil, Output: lines}
	}
}

func (a *Adapter) runAllTests() tea.Cmd {
	return func() tea.Msg {
		var allLines []string

		// Go tests first
		goBin, err := exec.LookPath("go")
		if err != nil {
			goBin = "/usr/local/go/bin/go"
		}
		goCmd := exec.Command(goBin, "test", "-tags", "goolm", "-count=1", "./...")
		goCmd.Env = a.mgr.BuildEnv()
		goOut, goErr := goCmd.CombinedOutput()

		allLines = append(allLines, "═══ Go Tests ═══")
		goOutput := strings.TrimSpace(string(goOut))
		if goOutput == "" && goErr != nil {
			goOutput = "Error: " + goErr.Error()
		}
		allLines = append(allLines, strings.Split(goOutput, "\n")...)

		if goErr != nil {
			allLines = append(allLines, "", "Go tests FAILED — skipping E2E")
			return puretui.MsgTestsDone{Kind: puretui.TestKindAll, Passed: false, Output: allLines}
		}

		// E2E tests
		allLines = append(allLines, "", "═══ E2E Tests ═══")
		e2eCmd := exec.Command("bash", "./dev-scripts/e2e/run.sh")
		e2eCmd.Env = a.mgr.BuildEnv()
		e2eOut, e2eErr := e2eCmd.CombinedOutput()

		e2eOutput := strings.TrimSpace(string(e2eOut))
		if e2eOutput == "" && e2eErr != nil {
			e2eOutput = "Error: " + e2eErr.Error()
		}
		allLines = append(allLines, strings.Split(e2eOutput, "\n")...)

		return puretui.MsgTestsDone{Kind: puretui.TestKindAll, Passed: e2eErr == nil, Output: allLines}
	}
}

func (a *Adapter) tick() tea.Cmd {
	return tea.Tick(3*time.Second, func(time.Time) tea.Msg {
		return puretui.MsgTick{}
	})
}

// ── formatting helpers ───────────────────────────────────────────────────

func formatUptime(secs int64) string {
	if secs < 0 {
		return "n/a"
	}
	d := secs / 86400
	h := (secs % 86400) / 3600
	m := (secs % 3600) / 60
	if d > 0 {
		return fmt.Sprintf("%dd %dh", d, h)
	}
	if h > 0 {
		return fmt.Sprintf("%dh %dm", h, m)
	}
	return fmt.Sprintf("%dm", m)
}

func formatBytes(bytes int64) string {
	switch {
	case bytes >= 1<<30:
		return fmt.Sprintf("%.1f GB", float64(bytes)/float64(1<<30))
	case bytes >= 1<<20:
		return fmt.Sprintf("%.1f MB", float64(bytes)/float64(1<<20))
	case bytes >= 1<<10:
		return fmt.Sprintf("%.1f KB", float64(bytes)/float64(1<<10))
	default:
		return fmt.Sprintf("%d B", bytes)
	}
}
