// 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/exec"
	"strings"
	"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.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.runTests()

	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}
	}
}

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) runTests() tea.Cmd {
	return func() tea.Msg {
		goBin, err := exec.LookPath("go")
		if err != nil {
			// Fallback to known location
			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")
		passed := err == nil

		return puretui.MsgTestsDone{Passed: passed, Output: lines}
	}
}

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)
	}
}
