package ui

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"

	configstore "github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/internal/config"
	picoclawconfig "github.com/sipeed/picoclaw/pkg/config"
)

type appState struct {
	app         *tview.Application
	pages       *tview.Pages
	stack       []string
	config      *picoclawconfig.Config
	configPath  string
	gatewayCmd  *exec.Cmd
	menus       map[string]*Menu
	original    []byte
	hasOriginal bool
	backupPath  string
	dirty       bool
	logPath     string
}

func Run() error {
	applyStyles()
	cfg, err := configstore.Load()
	if err != nil {
		return err
	}
	path, err := configstore.ConfigPath()
	if err != nil {
		return err
	}

	if cfg == nil {
		cfg = picoclawconfig.DefaultConfig()
	}

	originalData, hasOriginal := loadOriginalConfig(path)
	backupPath := path + ".bak"
	if hasOriginal {
		_ = writeBackupConfig(backupPath, originalData)
	}

	logPath := filepath.Join(filepath.Dir(path), "gateway.log")
	state := &appState{
		app:         tview.NewApplication(),
		pages:       tview.NewPages(),
		config:      cfg,
		configPath:  path,
		menus:       map[string]*Menu{},
		original:    originalData,
		hasOriginal: hasOriginal,
		backupPath:  backupPath,
		logPath:     logPath,
	}

	state.push("main", state.mainMenu())

	root := tview.NewFlex().SetDirection(tview.FlexRow)
	root.AddItem(bannerView(), 6, 0, false)
	root.AddItem(state.pages, 0, 1, true)
	root.AddItem(footerView(), 1, 0, false)

	if err := state.app.SetRoot(root, true).EnableMouse(false).Run(); err != nil {
		return err
	}
	return nil
}

func (s *appState) push(name string, primitive tview.Primitive) {
	s.pages.AddPage(name, primitive, true, true)
	s.stack = append(s.stack, name)
	s.pages.SwitchToPage(name)
	if menu, ok := primitive.(*Menu); ok {
		s.menus[name] = menu
	}
}

func (s *appState) pop() {
	if len(s.stack) == 0 {
		return
	}
	last := s.stack[len(s.stack)-1]
	s.pages.RemovePage(last)
	s.stack = s.stack[:len(s.stack)-1]
	if len(s.stack) == 0 {
		s.app.Stop()
		return
	}
	current := s.stack[len(s.stack)-1]
	s.pages.SwitchToPage(current)
	if menu, ok := s.menus[current]; ok {
		s.refreshMenu(current, menu)
	}
}

func (s *appState) mainMenu() tview.Primitive {
	menu := NewMenu("Menu", nil)
	refreshMainMenu(menu, s)
	menu.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyEsc:
			s.requestExit()
			return nil
		}

		return event
	})

	return menu
}

func (s *appState) refreshMenu(name string, menu *Menu) {
	switch name {
	case "main":
		refreshMainMenu(menu, s)
	case "model":
		refreshModelMenuFromState(menu, s)
	case "channel":
		refreshChannelMenuFromState(menu, s)
	}
}

func (s *appState) countChannels() (enabled int, total int) {
	c := s.config.Channels
	entries := []bool{
		c.Telegram.Enabled,
		c.Discord.Enabled,
		c.QQ.Enabled,
		c.MaixCam.Enabled,
		c.WhatsApp.Enabled,
		c.Feishu.Enabled,
		c.DingTalk.Enabled,
		c.Slack.Enabled,
		c.Matrix.Enabled,
		c.LINE.Enabled,
		c.OneBot.Enabled,
		c.WeCom.Enabled,
		c.WeComApp.Enabled,
	}
	total = len(entries)
	for _, v := range entries {
		if v {
			enabled++
		}
	}
	return enabled, total
}

func refreshMainMenuIfPresent(s *appState) {
	if menu, ok := s.menus["main"]; ok {
		refreshMainMenu(menu, s)
	}
}

func refreshMainMenu(menu *Menu, s *appState) {
	selectedModel := s.selectedModelName()
	modelReady := selectedModel != ""
	channelReady := s.hasEnabledChannel()
	enabledCount, totalChannels := s.countChannels()
	gatewayRunning := s.gatewayCmd != nil || s.isGatewayRunning()

	gatewayLabel := "Start Gateway"
	gatewayDescription := "Launch gateway for channels"
	if gatewayRunning {
		gatewayLabel = "Stop Gateway"
		gatewayDescription = "Gateway running"
	}

	items := []MenuItem{
		{
			Label:       rootModelLabel(selectedModel),
			Description: rootModelDescription(),
			Action: func() {
				s.push("model", s.modelMenu())
			},
			MainColor: func() *tcell.Color {
				if modelReady {
					return nil
				}
				color := tcell.ColorGray
				return &color
			}(),
		},
		{
			Label:       rootChannelLabel(channelReady),
			Description: fmt.Sprintf("%d/%d enabled", enabledCount, totalChannels),
			Action: func() {
				s.push("channel", s.channelMenu())
			},
			MainColor: func() *tcell.Color {
				if channelReady {
					return nil
				}
				color := tcell.ColorGray
				return &color
			}(),
		},
		{
			Label:       "Start Talk",
			Description: "Open picoclaw agent in terminal",
			Action: func() {
				s.requestStartTalk()
			},
			Disabled: !modelReady,
		},
		{
			Label:       gatewayLabel,
			Description: gatewayDescription,
			Action: func() {
				if gatewayRunning {
					s.stopGateway()
				} else {
					s.requestStartGateway()
				}
				refreshMainMenu(menu, s)
			},
			Disabled: !gatewayRunning && (!modelReady || !channelReady),
		},
		{
			Label:       "View Gateway Log",
			Description: "Open gateway.log",
			Action: func() {
				s.viewGatewayLog()
			},
		},
		{
			Label:       "Exit",
			Description: "Exit the TUI",
			Action: func() {
				s.requestExit()
			},
		},
	}
	menu.applyItems(items)
}

func (s *appState) applyChangesValidated() bool {
	if err := s.config.ValidateModelList(); err != nil {
		s.showMessage("Validation failed", err.Error())
		return false
	}
	if err := s.validateAgentModel(); err != nil {
		s.showMessage("Validation failed", err.Error())
		return false
	}
	if err := configstore.Save(s.config); err != nil {
		s.showMessage("Save failed", err.Error())
		return false
	}
	if data, err := os.ReadFile(s.configPath); err == nil {
		s.original = data
		s.hasOriginal = true
		_ = writeBackupConfig(s.backupPath, data)
	}
	return true
}

func (s *appState) requestExit() {
	if s.dirty {
		s.confirmApplyOrDiscard(func() {
			s.app.Stop()
		}, func() {
			s.discardChanges()
			s.app.Stop()
		})
		return
	}
	s.app.Stop()
}

func (s *appState) requestStartTalk() {
	if s.dirty {
		s.confirmApplyOrDiscard(func() {
			s.startTalk()
		}, func() {
			s.startTalk()
		})
		return
	}
	s.startTalk()
}

func (s *appState) requestStartGateway() {
	if s.dirty {
		s.confirmApplyOrDiscard(func() {
			s.startGateway()
		}, func() {
			s.startGateway()
		})
		return
	}
	s.startGateway()
}

func (s *appState) viewGatewayLog() {
	data, err := os.ReadFile(s.logPath)
	if err != nil {
		s.showMessage("Log not found", "gateway.log not found")
		return
	}
	text := tview.NewTextView()
	text.SetBorder(true).SetTitle("Gateway Log")
	text.SetText(string(data))
	text.SetDoneFunc(func(key tcell.Key) {
		s.pages.RemovePage("log")
	})
	text.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		if event.Key() == tcell.KeyEsc {
			s.pages.RemovePage("log")
			return nil
		}
		return event
	})
	s.pages.AddPage("log", text, true, true)
}

func (s *appState) selectedModelName() string {
	modelName := strings.TrimSpace(s.config.Agents.Defaults.Model)
	if modelName == "" {
		return ""
	}
	if !s.isActiveModelValid() {
		return ""
	}
	return modelName
}

func rootModelLabel(selected string) string {
	if selected == "" {
		return "Model (None)"
	}
	return "Model (" + selected + ")"
}

func rootModelDescription() string {
	return "Using SPACE to choose your model"
}

func rootChannelLabel(valid bool) string {
	if !valid {
		return "Channel (no channel enabled)"
	}
	return "Channel"
}

func (s *appState) startTalk() {
	if !s.isActiveModelValid() {
		s.showMessage("Model required", "Select a valid model before starting talk")
		return
	}
	if !s.applyChangesValidated() {
		return
	}
	s.app.Suspend(func() {
		cmd := exec.Command("picoclaw", "agent")
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		_ = cmd.Run()
	})
}

func (s *appState) startGateway() {
	if !s.isActiveModelValid() {
		s.showMessage("Model required", "Select a valid model before starting gateway")
		return
	}
	if !s.hasEnabledChannel() {
		s.showMessage("Channel required", "Enable at least one channel before starting gateway")
		return
	}
	if !s.applyChangesValidated() {
		return
	}
	_ = stopGatewayProcess()
	cmd := exec.Command("picoclaw", "gateway")
	logFile, err := os.OpenFile(s.logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
	if err != nil {
		s.showMessage("Gateway failed", err.Error())
		return
	}
	cmd.Stdout = logFile
	cmd.Stderr = logFile
	if err := cmd.Start(); err != nil {
		s.showMessage("Gateway failed", err.Error())
		_ = logFile.Close()
		return
	}
	_ = logFile.Close()
	s.gatewayCmd = cmd
}

func (s *appState) stopGateway() {
	_ = stopGatewayProcess()
	if s.gatewayCmd != nil && s.gatewayCmd.Process != nil {
		_ = s.gatewayCmd.Process.Kill()
	}
	s.gatewayCmd = nil
}

func (s *appState) isGatewayRunning() bool {
	return isGatewayProcessRunning()
}

func (s *appState) validateAgentModel() error {
	modelName := strings.TrimSpace(s.config.Agents.Defaults.Model)
	if modelName == "" {
		return nil
	}
	_, err := s.config.GetModelConfig(modelName)
	return err
}

func (s *appState) isActiveModelValid() bool {
	modelName := strings.TrimSpace(s.config.Agents.Defaults.Model)
	if modelName == "" {
		return false
	}
	cfg, err := s.config.GetModelConfig(modelName)
	if err != nil {
		return false
	}
	hasKey := strings.TrimSpace(cfg.APIKey) != "" || strings.TrimSpace(cfg.AuthMethod) == "oauth"
	hasModel := strings.TrimSpace(cfg.Model) != ""
	return hasKey && hasModel
}

func (s *appState) hasEnabledChannel() bool {
	c := s.config.Channels
	return c.Telegram.Enabled || c.Discord.Enabled || c.QQ.Enabled || c.MaixCam.Enabled ||
		c.WhatsApp.Enabled || c.Feishu.Enabled || c.DingTalk.Enabled || c.Slack.Enabled ||
		c.Matrix.Enabled || c.LINE.Enabled || c.OneBot.Enabled || c.WeCom.Enabled || c.WeComApp.Enabled
}

func (s *appState) confirmApplyOrDiscard(onApply func(), onDiscard func()) {
	if s.pages.HasPage("apply") {
		return
	}
	modal := tview.NewModal().
		SetText("Apply changes or discard before continuing?").
		AddButtons([]string{"Cancel", "Discard", "Apply"}).
		SetDoneFunc(func(buttonIndex int, buttonLabel string) {
			s.pages.RemovePage("apply")
			switch buttonLabel {
			case "Discard":
				s.discardChanges()
				if onDiscard != nil {
					onDiscard()
				}
			case "Apply":
				if s.applyChangesValidated() {
					s.dirty = false
					if onApply != nil {
						onApply()
					}
				}
			}
		})
	modal.SetBorder(true)
	s.pages.AddPage("apply", modal, true, true)
}

func (s *appState) discardChanges() {
	if s.hasOriginal {
		_ = writeOriginalConfig(s.configPath, s.original)
	} else {
		_ = os.Remove(s.configPath)
	}
	_ = os.Remove(s.backupPath)
	if cfg, err := configstore.Load(); err == nil && cfg != nil {
		s.config = cfg
	}
	s.dirty = false
	refreshMainMenuIfPresent(s)
}

func (s *appState) showMessage(title, message string) {
	if s.pages.HasPage("message") {
		return
	}
	modal := tview.NewModal().
		SetText(strings.TrimSpace(message)).
		AddButtons([]string{"OK"}).
		SetDoneFunc(func(_ int, _ string) {
			s.pages.RemovePage("message")
		})
	modal.SetTitle(title).SetBorder(true)
	modal.SetBackgroundColor(tview.Styles.ContrastBackgroundColor)
	modal.SetTextColor(tview.Styles.PrimaryTextColor)
	modal.SetButtonBackgroundColor(tcell.NewRGBColor(112, 102, 255))
	modal.SetButtonTextColor(tview.Styles.PrimaryTextColor)
	s.pages.AddPage("message", modal, true, true)
}

func loadOriginalConfig(path string) ([]byte, bool) {
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, false
		}
		return nil, false
	}
	return data, true
}

func writeOriginalConfig(path string, data []byte) error {
	return os.WriteFile(path, data, 0o600)
}

func writeBackupConfig(path string, data []byte) error {
	return os.WriteFile(path, data, 0o600)
}
