package ui

import (
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

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

	picoclawconfig "github.com/sipeed/picoclaw/pkg/config"
)

func (s *appState) modelMenu() tview.Primitive {
	items := make([]MenuItem, 0, 1+len(s.config.ModelList))
	currentModel := strings.TrimSpace(s.config.Agents.Defaults.Model)
	for i := range s.config.ModelList {
		index := i
		model := s.config.ModelList[i]
		isValid := isModelValid(model)
		desc := model.APIBase
		if desc == "" {
			desc = model.AuthMethod
		}
		if desc == "" {
			desc = "api_key required"
		}
		label := fmt.Sprintf("%s (%s)", model.ModelName, model.Model)
		if model.ModelName == currentModel && currentModel != "" {
			label = "* " + label
		}
		isSelected := model.ModelName == currentModel && currentModel != ""
		items = append(items, MenuItem{
			Label:       label,
			Description: desc,
			MainColor:   modelStatusColor(isValid, isSelected),
			Action: func() {
				s.push(fmt.Sprintf("model-%d", index), s.modelForm(index))
			},
		})
	}
	// Add model entry appended at the end so the models map to rows 1..N
	items = append(items,
		MenuItem{
			Label:       "**Add model**",
			Description: "Append a new model entry",
			Action: func() {
				newName := s.nextAvailableModelName("new-model")
				s.addModel(
					picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.4"},
				)
				s.push(
					fmt.Sprintf("model-%d", len(s.config.ModelList)-1),
					s.modelForm(len(s.config.ModelList)-1),
				)
			},
		},
	)

	menu := NewMenu("Models", items)
	menu.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		if event.Key() == tcell.KeyEsc {
			s.pop()
			return nil
		}

		if event.Rune() == ' ' {
			row, _ := menu.GetSelection()
			if row >= 0 && row < len(s.config.ModelList) {
				model := s.config.ModelList[row]
				if !isModelValid(model) {
					s.showMessage(
						"Invalid model",
						"Select a model with api_key or oauth auth_method",
					)
					return nil
				}
				s.config.Agents.Defaults.Model = model.ModelName
				s.dirty = true
				refreshModelMenu(menu, s.config.Agents.Defaults.Model, s.config.ModelList)
				refreshMainMenuIfPresent(s)
			}
			return nil
		}
		return event
	})
	return menu
}

func (s *appState) modelForm(index int) tview.Primitive {
	model := &s.config.ModelList[index]
	form := tview.NewForm()
	form.SetBorder(true).SetTitle(fmt.Sprintf("Model: %s", model.ModelName))

	addInput(form, "Model Name", model.ModelName, func(value string) {
		if value == "" {
			s.showMessage("Invalid model name", "Model Name cannot be empty")
			return
		}
		if s.modelNameExists(value, index) {
			s.showMessage("Duplicate model name", fmt.Sprintf("Model Name '%s' already exists", value))
			return
		}
		oldName := model.ModelName
		model.ModelName = value
		if s.config.Agents.Defaults.Model == oldName {
			s.config.Agents.Defaults.Model = value
		}
		s.dirty = true
		form.SetTitle(fmt.Sprintf("Model: %s", model.ModelName))
		refreshMainMenuIfPresent(s)
		if menu, ok := s.menus["model"]; ok {
			refreshModelMenuFromState(menu, s)
		}
	})
	addInput(form, "Model", model.Model, func(value string) {
		model.Model = value
		s.dirty = true
		refreshMainMenuIfPresent(s)
		if menu, ok := s.menus["model"]; ok {
			refreshModelMenuFromState(menu, s)
		}
	})
	addInput(form, "API Base", model.APIBase, func(value string) {
		model.APIBase = value
		s.dirty = true
		refreshMainMenuIfPresent(s)
		if menu, ok := s.menus["model"]; ok {
			refreshModelMenuFromState(menu, s)
		}
	})
	addInput(form, "API Key", model.APIKey, func(value string) {
		model.APIKey = value
		s.dirty = true
		refreshMainMenuIfPresent(s)
		if menu, ok := s.menus["model"]; ok {
			refreshModelMenuFromState(menu, s)
		}
	})
	addInput(form, "Proxy", model.Proxy, func(value string) {
		model.Proxy = value
	})
	addInput(form, "Auth Method", model.AuthMethod, func(value string) {
		model.AuthMethod = value
		s.dirty = true
		refreshMainMenuIfPresent(s)
		if menu, ok := s.menus["model"]; ok {
			refreshModelMenuFromState(menu, s)
		}
	})
	addInput(form, "Connect Mode", model.ConnectMode, func(value string) {
		model.ConnectMode = value
	})
	addInput(form, "Workspace", model.Workspace, func(value string) {
		model.Workspace = value
	})
	addInput(form, "Max Tokens Field", model.MaxTokensField, func(value string) {
		model.MaxTokensField = value
	})
	addIntInput(form, "RPM", model.RPM, func(value int) {
		model.RPM = value
	})
	addIntInput(form, "Request Timeout", model.RequestTimeout, func(value int) {
		model.RequestTimeout = value
	})

	form.AddButton("Delete", func() {
		pageName := "confirm-delete-model"
		if s.pages.HasPage(pageName) {
			return
		}
		modal := tview.NewModal().
			SetText("Are you sure you want to delete this model?").
			AddButtons([]string{"Cancel", "Delete"}).
			SetDoneFunc(func(buttonIndex int, buttonLabel string) {
				s.pages.RemovePage(pageName)
				if buttonLabel == "Delete" {
					s.deleteModel(index)
				}
			})
		modal.SetTitle("Confirm Delete").SetBorder(true)
		s.pages.AddPage(pageName, modal, true, true)
	})
	form.AddButton("Test", func() {
		s.testModel(model)
	})
	form.AddButton("Back", func() {
		s.pop()
	})

	form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		if event.Key() == tcell.KeyEsc {
			s.pop()
			return nil
		}
		return event
	})
	return form
}

func addInput(form *tview.Form, label, value string, onChange func(string)) {
	form.AddInputField(label, value, 128, nil, func(text string) {
		onChange(strings.TrimSpace(text))
	})
}

func addIntInput(form *tview.Form, label string, value int, onChange func(int)) {
	form.AddInputField(label, fmt.Sprintf("%d", value), 16, nil, func(text string) {
		var parsed int
		if _, err := fmt.Sscanf(strings.TrimSpace(text), "%d", &parsed); err == nil {
			onChange(parsed)
		}
	})
}

func (s *appState) addModel(model picoclawconfig.ModelConfig) {
	s.config.ModelList = append(s.config.ModelList, model)
}

func (s *appState) deleteModel(index int) {
	if index < 0 || index >= len(s.config.ModelList) {
		return
	}
	s.config.ModelList = append(s.config.ModelList[:index], s.config.ModelList[index+1:]...)
	s.pop()
}

func modelStatusColor(valid bool, selected bool) *tcell.Color {
	if valid {
		color := tview.Styles.PrimaryTextColor
		return &color
	}
	color := tcell.ColorGray
	return &color
}

func refreshModelMenu(menu *Menu, currentModel string, models []picoclawconfig.ModelConfig) {
	for i, model := range models {
		row := i
		label := fmt.Sprintf("%s (%s)", model.ModelName, model.Model)
		isValid := isModelValid(model)
		if model.ModelName == currentModel && currentModel != "" {
			label = "* " + label
		}
		cell := menu.GetCell(row, 0)
		if cell != nil {
			cell.SetText(label)
			isSelected := model.ModelName == currentModel && currentModel != ""
			color := modelStatusColor(isValid, isSelected)
			if color != nil {
				cell.SetTextColor(*color)
			}
		}
	}
}

func refreshModelMenuFromState(menu *Menu, s *appState) {
	items := make([]MenuItem, 0, 1+len(s.config.ModelList))
	currentModel := strings.TrimSpace(s.config.Agents.Defaults.Model)
	for i := range s.config.ModelList {
		index := i
		model := s.config.ModelList[i]
		isValid := isModelValid(model)
		desc := model.APIBase
		if desc == "" {
			desc = model.AuthMethod
		}
		if desc == "" {
			desc = "api_key required"
		}
		label := fmt.Sprintf("%s (%s)", model.ModelName, model.Model)
		if model.ModelName == currentModel && currentModel != "" {
			label = "* " + label
		}
		isSelected := model.ModelName == currentModel && currentModel != ""
		items = append(items, MenuItem{
			Label:       label,
			Description: desc,
			MainColor:   modelStatusColor(isValid, isSelected),
			Action: func() {
				s.push(fmt.Sprintf("model-%d", index), s.modelForm(index))
			},
		})
	}
	items = append(items,
		MenuItem{
			Label:       "**Add Model**",
			Description: "Append a new model entry",
			Action: func() {
				newName := s.nextAvailableModelName("new-model")
				s.addModel(
					picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.4"},
				)
				s.push(fmt.Sprintf("model-%d", len(s.config.ModelList)-1), s.modelForm(len(s.config.ModelList)-1))
			},
		},
	)
	menu.applyItems(items)
}

func isModelValid(model picoclawconfig.ModelConfig) bool {
	hasKey := strings.TrimSpace(model.APIKey) != "" ||
		strings.TrimSpace(model.AuthMethod) == "oauth"
	hasModel := strings.TrimSpace(model.Model) != ""
	return hasKey && hasModel
}

func (s *appState) modelNameExists(name string, excludeIndex int) bool {
	target := strings.TrimSpace(name)
	if target == "" {
		return false
	}
	for i := range s.config.ModelList {
		if i == excludeIndex {
			continue
		}
		if strings.TrimSpace(s.config.ModelList[i].ModelName) == target {
			return true
		}
	}
	return false
}

func (s *appState) nextAvailableModelName(base string) string {
	name := strings.TrimSpace(base)
	if name == "" {
		name = "new-model"
	}
	if !s.modelNameExists(name, -1) {
		return name
	}
	for i := 2; ; i++ {
		candidate := fmt.Sprintf("%s-%d", name, i)
		if !s.modelNameExists(candidate, -1) {
			return candidate
		}
	}
}

func (s *appState) testModel(model *picoclawconfig.ModelConfig) {
	if model == nil {
		return
	}
	if strings.TrimSpace(model.APIKey) == "" {
		s.showMessage("Missing API Key", "Set api_key before testing")
		return
	}
	base := strings.TrimSpace(model.APIBase)
	if base == "" {
		s.showMessage("Missing API Base", "Set api_base before testing")
		return
	}
	modelID := strings.TrimSpace(model.Model)
	if modelID == "" {
		s.showMessage("Missing Model", "Set model before testing")
		return
	}
	if !strings.HasPrefix(modelID, "openai/") {
		s.showMessage("Unsupported model", "Only openai/* models are supported for test")
		return
	}
	modelName := strings.TrimPrefix(modelID, "openai/")
	endpoint := strings.TrimRight(base, "/") + "/chat/completions"

	payload := fmt.Sprintf(
		`{"model":"%s","messages":[{"role":"user","content":"ping"}],"max_tokens":1}`,
		modelName,
	)
	client := &http.Client{Timeout: 10 * time.Second}
	request, err := http.NewRequest("POST", endpoint, strings.NewReader(payload))
	if err != nil {
		s.showMessage("Test failed", err.Error())
		return
	}
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Authorization", "Bearer "+strings.TrimSpace(model.APIKey))

	resp, err := client.Do(request)
	if err != nil {
		s.showMessage("Test failed", err.Error())
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		s.showMessage("Test OK", resp.Status)
		return
	}
	body, err := io.ReadAll(io.LimitReader(resp.Body, 2048))
	if err != nil {
		s.showMessage("Test failed", fmt.Sprintf("failed to read response: %v", err))
		return
	}
	s.showMessage(
		"Test failed",
		fmt.Sprintf("%s: %s", resp.Status, strings.TrimSpace(string(body))),
	)
}
