package main

import (
	"fmt"
	"io"
	"strings"

	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

type tuiTheme struct {
	outer          lipgloss.Style
	headerBar      lipgloss.Style
	badge          lipgloss.Style
	chrome         lipgloss.Style
	title          lipgloss.Style
	subtitle       lipgloss.Style
	sectionTitle   lipgloss.Style
	body           lipgloss.Style
	muted          lipgloss.Style
	footer         lipgloss.Style
	panel          lipgloss.Style
	panelAccent    lipgloss.Style
	row            lipgloss.Style
	rowSelected    lipgloss.Style
	rowNumber      lipgloss.Style
	rowNumberOn    lipgloss.Style
	rowKey         lipgloss.Style
	rowKeySelected lipgloss.Style
	rowTitle       lipgloss.Style
	rowTitleOn     lipgloss.Style
	rowHint        lipgloss.Style
	rowHintActive  lipgloss.Style
	statusInfo     lipgloss.Style
	statusWarn     lipgloss.Style
	statusSuccess  lipgloss.Style
	inputLabel     lipgloss.Style
	inputBox       lipgloss.Style
}

func newTUITheme() tuiTheme {
	border := lipgloss.Color("#5A6772")
	borderSoft := lipgloss.Color("#31414A")
	text := lipgloss.Color("#ECE4D8")
	muted := lipgloss.Color("#A49788")
	accent := lipgloss.Color("#D9733F")
	accentSoft := lipgloss.Color("#F2D2B5")
	panel := lipgloss.Color("#161C20")
	panelAlt := lipgloss.Color("#212A30")
	panelLift := lipgloss.Color("#2B363D")
	info := lipgloss.Color("#86B7C6")
	warn := lipgloss.Color("#D8AB63")
	success := lipgloss.Color("#91B57F")

	return tuiTheme{
		outer: lipgloss.NewStyle().
			Border(lipgloss.RoundedBorder()).
			BorderForeground(border).
			Padding(1, 2),
		headerBar: lipgloss.NewStyle().
			Foreground(borderSoft),
		chrome: lipgloss.NewStyle().
			Foreground(muted),
		badge: lipgloss.NewStyle().
			Foreground(panel).
			Background(accentSoft).
			Bold(true).
			Padding(0, 1),
		subtitle: lipgloss.NewStyle().
			Foreground(muted),
		title: lipgloss.NewStyle().
			Foreground(text).
			Bold(true),
		sectionTitle: lipgloss.NewStyle().
			Foreground(accentSoft).
			Bold(true).
			Border(lipgloss.NormalBorder(), false, false, true, false).
			BorderForeground(borderSoft).
			PaddingBottom(0).
			MarginBottom(1),
		body: lipgloss.NewStyle().
			Foreground(text),
		muted: lipgloss.NewStyle().
			Foreground(muted),
		footer: lipgloss.NewStyle().
			Foreground(muted).
			Border(lipgloss.NormalBorder(), true, false, false, false).
			BorderForeground(borderSoft).
			PaddingTop(1).
			MarginTop(1),
		panel: lipgloss.NewStyle().
			Border(lipgloss.NormalBorder(), false, false, false, true).
			BorderForeground(borderSoft).
			PaddingLeft(1).
			MarginBottom(1),
		panelAccent: lipgloss.NewStyle().
			Border(lipgloss.ThickBorder(), false, false, false, true).
			BorderForeground(accent).
			PaddingLeft(1).
			MarginBottom(1),
		row: lipgloss.NewStyle().
			Border(lipgloss.NormalBorder(), false, false, false, true).
			BorderForeground(panelLift).
			Padding(0, 1).
			MarginBottom(1),
		rowSelected: lipgloss.NewStyle().
			Border(lipgloss.ThickBorder(), false, false, false, true).
			BorderForeground(accent).
			Background(panel).
			Padding(0, 1).
			MarginBottom(1),
		rowNumber: lipgloss.NewStyle().
			Foreground(muted),
		rowNumberOn: lipgloss.NewStyle().
			Foreground(accentSoft).
			Bold(true),
		rowKey: lipgloss.NewStyle().
			Foreground(accent).
			Bold(true).
			Background(panelAlt).
			Padding(0, 1),
		rowKeySelected: lipgloss.NewStyle().
			Foreground(accentSoft).
			Background(panelLift).
			Bold(true).
			Padding(0, 1),
		rowTitle: lipgloss.NewStyle().
			Foreground(text).
			Bold(true),
		rowTitleOn: lipgloss.NewStyle().
			Foreground(text).
			Bold(true),
		rowHint: lipgloss.NewStyle().
			Foreground(muted),
		rowHintActive: lipgloss.NewStyle().
			Foreground(text),
		statusInfo: lipgloss.NewStyle().
			Foreground(info).
			Bold(true),
		statusWarn: lipgloss.NewStyle().
			Foreground(warn).
			Bold(true),
		statusSuccess: lipgloss.NewStyle().
			Foreground(success).
			Bold(true),
		inputLabel: lipgloss.NewStyle().
			Foreground(accentSoft).
			Bold(true),
		inputBox: lipgloss.NewStyle().
			Border(lipgloss.RoundedBorder()).
			BorderForeground(border).
			Padding(0, 1).
			Background(panelAlt),
	}
}

func clampFrameWidth(windowWidth int) int {
	const fallback = 84
	if windowWidth <= 0 {
		return fallback
	}
	if windowWidth <= 44 {
		return windowWidth
	}
	if windowWidth > 92 {
		return 92
	}
	return windowWidth - 2
}

func innerWidth(frameWidth int) int {
	if frameWidth <= 10 {
		return frameWidth
	}
	return frameWidth - 6
}

func (t tuiTheme) renderFrame(width int, title, subtitle, body, footer string) string {
	frameWidth := clampFrameWidth(width)
	contentWidth := innerWidth(frameWidth)
	placeWidth := width
	if placeWidth <= 0 {
		placeWidth = frameWidth
	}

	headerLeft := lipgloss.JoinHorizontal(lipgloss.Top, t.badge.Render("SAMBA CONFIGER"), "  ", t.chrome.Render("share editor"))
	headerRule := t.headerBar.Render(strings.Repeat("─", max(0, contentWidth-lipgloss.Width(headerLeft)-1)))
	header := lipgloss.JoinHorizontal(lipgloss.Center, headerLeft, " ", headerRule)

	sections := []string{header, t.title.Width(contentWidth).Render(title)}
	if subtitle != "" {
		sections = append(sections, t.subtitle.Width(contentWidth).Render(subtitle))
	}
	if body != "" {
		sections = append(sections, body)
	}
	if footer != "" {
		sections = append(sections, t.footer.Width(contentWidth).Render(footer))
	}

	return lipgloss.PlaceHorizontal(placeWidth, lipgloss.Center, t.outer.Width(frameWidth).Render(strings.Join(sections, "\n\n")))
}

func (t tuiTheme) renderSection(width int, title string, lines []string) string {
	if len(lines) == 0 {
		return ""
	}

	contentWidth := innerWidth(clampFrameWidth(width))
	rendered := make([]string, 0, len(lines)+1)
	if title != "" {
		rendered = append(rendered, t.sectionTitle.Width(contentWidth).Render(title))
	}
	for _, line := range lines {
		rendered = append(rendered, t.panel.Width(contentWidth).Render(t.body.Width(max(1, contentWidth-2)).Render(line)))
	}
	return strings.Join(rendered, "\n")
}

func (t tuiTheme) renderMessage(width int, kind, text string) string {
	label := t.statusInfo
	switch kind {
	case "warn":
		label = t.statusWarn
	case "success":
		label = t.statusSuccess
	}
	return t.panel.Width(innerWidth(clampFrameWidth(width))).Render(
		label.Render(strings.ToUpper(kind)) + "  " + t.body.Render(text),
	)
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

type menuOption struct {
	Key         string
	Value       string
	Label       string
	Description string
}

type menuModel struct {
	theme    tuiTheme
	title    string
	subtitle string
	intro    []string
	options  []menuOption
	cursor   int
	choice   string
	cancel   bool
	width    int
}

func (m menuModel) Init() tea.Cmd {
	return nil
}

func (m menuModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.width = msg.Width
	case tea.KeyMsg:
		key := strings.ToLower(msg.String())
		switch key {
		case "ctrl+c", "esc":
			m.cancel = true
			return m, tea.Quit
		case "up", "k":
			if len(m.options) > 0 {
				m.cursor = (m.cursor - 1 + len(m.options)) % len(m.options)
			}
		case "down", "j":
			if len(m.options) > 0 {
				m.cursor = (m.cursor + 1) % len(m.options)
			}
		case "enter":
			if len(m.options) > 0 {
				m.choice = m.options[m.cursor].Value
				return m, tea.Quit
			}
		default:
			for i, option := range m.options {
				if key == strings.ToLower(option.Key) || key == fmt.Sprintf("%d", i+1) {
					m.cursor = i
					m.choice = option.Value
					return m, tea.Quit
				}
			}
		}
	}
	return m, nil
}

func (m menuModel) View() string {
	width := clampFrameWidth(m.width)
	if width == 0 {
		width = clampFrameWidth(84)
	}
	contentWidth := innerWidth(width)

	rows := make([]string, 0, len(m.options))
	for i, option := range m.options {
		rowStyle := m.theme.row
		keyStyle := m.theme.rowKey
		titleStyle := m.theme.rowTitle
		hintStyle := m.theme.rowHint
		numberStyle := m.theme.rowNumber
		prefix := numberStyle.Render(fmt.Sprintf("%02d", i+1))
		if i == m.cursor {
			rowStyle = m.theme.rowSelected
			keyStyle = m.theme.rowKeySelected
			titleStyle = m.theme.rowTitleOn
			hintStyle = m.theme.rowHintActive
			numberStyle = m.theme.rowNumberOn
			prefix = numberStyle.Render(">>")
		}

		top := lipgloss.JoinHorizontal(
			lipgloss.Center,
			prefix,
			"  ",
			keyStyle.Render(strings.ToUpper(option.Key)),
			"  ",
			titleStyle.Render(option.Label),
		)

		rowLines := []string{top}
		if option.Description != "" && width >= 72 {
			rowLines = append(rowLines, hintStyle.Render(option.Description))
		}
		rows = append(rows, rowStyle.Width(contentWidth).Render(strings.Join(rowLines, "\n")))
	}

	bodyParts := []string{}
	if len(m.intro) > 0 {
		bodyParts = append(bodyParts, m.theme.renderSection(width, "Context", m.intro))
	}
	bodyParts = append(bodyParts, m.theme.sectionTitle.Width(contentWidth).Render("Actions"))
	bodyParts = append(bodyParts, strings.Join(rows, "\n"))

	return m.theme.renderFrame(
		m.width,
		m.title,
		m.subtitle,
		strings.Join(bodyParts, "\n\n"),
		"enter select  •  j/k or arrows move  •  letter/number picks instantly  •  esc cancel",
	)
}

type textPromptModel struct {
	theme        tuiTheme
	title        string
	subtitle     string
	label        string
	defaultValue string
	input        textinput.Model
	value        string
	cancel       bool
	width        int
}

func newTextPromptModel(theme tuiTheme, title, subtitle, label, defaultValue string) textPromptModel {
	input := textinput.New()
	input.Prompt = ""
	input.Placeholder = defaultValue
	input.Focus()
	input.CharLimit = 512
	input.Width = 48

	return textPromptModel{
		theme:        theme,
		title:        title,
		subtitle:     subtitle,
		label:        label,
		defaultValue: defaultValue,
		input:        input,
	}
}

func (m textPromptModel) Init() tea.Cmd {
	return textinput.Blink
}

func (m textPromptModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.width = msg.Width
		available := innerWidth(clampFrameWidth(m.width)) - 4
		if available < 16 {
			available = 16
		}
		m.input.Width = available
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "esc":
			m.cancel = true
			return m, tea.Quit
		case "enter":
			value := strings.TrimSpace(m.input.Value())
			if value == "" {
				value = m.defaultValue
			}
			m.value = value
			return m, tea.Quit
		}
	}

	var cmd tea.Cmd
	m.input, cmd = m.input.Update(msg)
	return m, cmd
}

func (m textPromptModel) View() string {
	width := clampFrameWidth(m.width)
	contentWidth := innerWidth(width)

	lines := []string{m.theme.inputLabel.Width(contentWidth).Render(m.label)}
	lines = append(lines, m.theme.panelAccent.Width(contentWidth).Render(m.theme.inputBox.Width(max(8, contentWidth-2)).Render(m.input.View())))
	if m.defaultValue != "" {
		lines = append(lines, m.theme.muted.Width(contentWidth).Render("Enter keeps: "+m.defaultValue))
	}

	return m.theme.renderFrame(
		m.width,
		m.title,
		m.subtitle,
		strings.Join(lines, "\n\n"),
		"enter confirm  •  esc cancel",
	)
}

func runMenuPrompt(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle string, intro []string, options []menuOption) (string, error) {
	model := menuModel{
		theme:    theme,
		title:    title,
		subtitle: subtitle,
		intro:    intro,
		options:  options,
	}

	result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out), tea.WithAltScreen()).Run()
	if err != nil {
		return "", err
	}

	final := result.(menuModel)
	if final.cancel {
		return "", ErrCancelled
	}
	return final.choice, nil
}

func runTextPrompt(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle, label, defaultValue string) (string, error) {
	model := newTextPromptModel(theme, title, subtitle, label, defaultValue)

	result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out), tea.WithAltScreen()).Run()
	if err != nil {
		return "", err
	}

	final := result.(textPromptModel)
	if final.cancel {
		return "", ErrCancelled
	}
	return final.value, nil
}

type cifsFormTextField struct {
	key          string
	label        string
	required     bool
	defaultValue string
	input        textinput.Model
}

type cifsFormToggle struct {
	label string
	value bool
}

type cifsMountFormModel struct {
	theme      tuiTheme
	title      string
	subtitle   string
	help       []string
	fields     []cifsFormTextField
	toggles    []cifsFormToggle
	cursor     int
	submitted  bool
	cancel     bool
	width      int
	errMessage string
	result     CIFSMountConfig
}

func newCIFSMountFormModel(theme tuiTheme, title, subtitle string, defaults CIFSMountConfig) cifsMountFormModel {
	makeInput := func(value string, password bool) textinput.Model {
		input := textinput.New()
		input.Prompt = ""
		input.SetValue(value)
		input.Focus()
		input.CharLimit = 512
		input.Width = 48
		if password {
			input.EchoMode = textinput.EchoPassword
			input.EchoCharacter = '•'
		}
		return input
	}

	fields := []cifsFormTextField{
		{key: "server", label: "Server name or IP", required: true, defaultValue: defaults.Server, input: makeInput(defaults.Server, false)},
		{key: "share", label: "Share name on that server", required: true, defaultValue: defaults.Share, input: makeInput(defaults.Share, false)},
		{key: "mount", label: "Local mount folder", required: true, defaultValue: defaults.MountPoint, input: makeInput(defaults.MountPoint, false)},
		{key: "username", label: "Username for the remote share", required: true, defaultValue: defaults.Username, input: makeInput(defaults.Username, false)},
		{key: "password", label: "Password for the remote share", required: true, defaultValue: defaults.Password, input: makeInput(defaults.Password, true)},
		{key: "domain", label: "Domain or workgroup", defaultValue: defaults.Domain, input: makeInput(defaults.Domain, false)},
		{key: "uid", label: "Local owner username or uid", defaultValue: defaults.UID, input: makeInput(defaults.UID, false)},
		{key: "gid", label: "Local group name or gid", defaultValue: defaults.GID, input: makeInput(defaults.GID, false)},
		{key: "filemode", label: "File permissions", defaultValue: defaultString(defaults.FileMode, "0664"), input: makeInput(defaultString(defaults.FileMode, "0664"), false)},
		{key: "dirmode", label: "Folder permissions", defaultValue: defaultString(defaults.DirMode, "0775"), input: makeInput(defaultString(defaults.DirMode, "0775"), false)},
	}

	model := cifsMountFormModel{
		theme:    theme,
		title:    title,
		subtitle: subtitle,
		help: []string{
			"Tab or arrows move between fields.",
			"Enter toggles switches or submits when Save is selected.",
		},
		fields: fields,
		toggles: []cifsFormToggle{
			{label: "Mount automatically at startup", value: defaults.AutoMount || (defaults.Server == "" && defaults.Share == "" && defaults.MountPoint == "")},
			{label: "Make this mount read-only", value: defaults.ReadOnly},
		},
	}
	model.focusCursor()
	return model
}

func (m *cifsMountFormModel) totalItems() int {
	return len(m.fields) + len(m.toggles) + 2
}

func (m *cifsMountFormModel) focusCursor() {
	for i := range m.fields {
		if i == m.cursor {
			m.fields[i].input.Focus()
		} else {
			m.fields[i].input.Blur()
		}
	}
}

func (m *cifsMountFormModel) moveCursor(delta int) {
	total := m.totalItems()
	if total == 0 {
		return
	}
	m.cursor = (m.cursor + delta + total) % total
	m.focusCursor()
	m.errMessage = ""
}

func (m *cifsMountFormModel) currentConfig() CIFSMountConfig {
	values := map[string]string{}
	for _, field := range m.fields {
		value := strings.TrimSpace(field.input.Value())
		if value == "" {
			value = strings.TrimSpace(field.defaultValue)
		}
		values[field.key] = value
	}

	return CIFSMountConfig{
		Server:     values["server"],
		Share:      values["share"],
		MountPoint: values["mount"],
		Username:   values["username"],
		Password:   values["password"],
		Domain:     values["domain"],
		UID:        values["uid"],
		GID:        values["gid"],
		FileMode:   values["filemode"],
		DirMode:    values["dirmode"],
		AutoMount:  m.toggles[0].value,
		ReadOnly:   m.toggles[1].value,
	}
}

func (m *cifsMountFormModel) validate() string {
	for _, field := range m.fields {
		if !field.required {
			continue
		}
		value := strings.TrimSpace(field.input.Value())
		if value == "" {
			value = strings.TrimSpace(field.defaultValue)
		}
		if value == "" {
			return field.label + " is required."
		}
	}
	return ""
}

func (m cifsMountFormModel) Init() tea.Cmd {
	return textinput.Blink
}

func (m cifsMountFormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.width = msg.Width
		available := innerWidth(clampFrameWidth(m.width)) - 8
		if available < 16 {
			available = 16
		}
		for i := range m.fields {
			m.fields[i].input.Width = available
		}
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "esc":
			m.cancel = true
			return m, tea.Quit
		case "shift+tab", "up", "k":
			m.moveCursor(-1)
			return m, nil
		case "tab", "down", "j":
			m.moveCursor(1)
			return m, nil
		case "enter":
			textCount := len(m.fields)
			toggleStart := textCount
			saveIndex := textCount + len(m.toggles)
			cancelIndex := saveIndex + 1

			switch {
			case m.cursor < textCount:
				m.moveCursor(1)
				return m, nil
			case m.cursor >= toggleStart && m.cursor < saveIndex:
				idx := m.cursor - toggleStart
				m.toggles[idx].value = !m.toggles[idx].value
				return m, nil
			case m.cursor == saveIndex:
				if errMessage := m.validate(); errMessage != "" {
					m.errMessage = errMessage
					return m, nil
				}
				m.result = m.currentConfig()
				m.submitted = true
				return m, tea.Quit
			case m.cursor == cancelIndex:
				m.cancel = true
				return m, tea.Quit
			}
		}
	}

	if m.cursor < len(m.fields) {
		var cmd tea.Cmd
		m.fields[m.cursor].input, cmd = m.fields[m.cursor].input.Update(msg)
		return m, cmd
	}

	return m, nil
}

func (m cifsMountFormModel) View() string {
	width := clampFrameWidth(m.width)
	if width == 0 {
		width = clampFrameWidth(84)
	}
	contentWidth := innerWidth(width)

	formRows := make([]string, 0, len(m.fields)+len(m.toggles)+3)
	for i, field := range m.fields {
		active := i == m.cursor
		boxStyle := m.theme.panel
		if active {
			boxStyle = m.theme.panelAccent
		}

		label := field.label
		if field.required {
			label += " *"
		}
		fieldLines := []string{m.theme.inputLabel.Render(label)}
		fieldLines = append(fieldLines, m.theme.inputBox.Width(max(12, contentWidth-6)).Render(field.input.View()))
		formRows = append(formRows, boxStyle.Width(contentWidth).Render(strings.Join(fieldLines, "\n")))
	}

	toggleStart := len(m.fields)
	for i, toggle := range m.toggles {
		active := toggleStart+i == m.cursor
		rowStyle := m.theme.row
		marker := "[ ]"
		if toggle.value {
			marker = "[x]"
		}
		if active {
			rowStyle = m.theme.rowSelected
		}
		formRows = append(formRows, rowStyle.Width(contentWidth).Render(
			m.theme.rowTitle.Render(marker+" "+toggle.label),
		))
	}

	saveIndex := len(m.fields) + len(m.toggles)
	saveStyle := m.theme.row
	cancelStyle := m.theme.row
	if m.cursor == saveIndex {
		saveStyle = m.theme.rowSelected
	}
	if m.cursor == saveIndex+1 {
		cancelStyle = m.theme.rowSelected
	}
	formRows = append(formRows, saveStyle.Width(contentWidth).Render(m.theme.rowTitle.Render("Save mount")))
	formRows = append(formRows, cancelStyle.Width(contentWidth).Render(m.theme.rowTitle.Render("Cancel")))

	previewCfg := m.currentConfig()
	previewLines := []string{
		buildFstabLine(previewCfg),
		"Passwords are written directly into /etc/fstab by this workflow.",
	}
	bodyParts := []string{
		m.theme.renderSection(width, "Workflow", m.help),
		m.theme.sectionTitle.Width(contentWidth).Render("Mount Details"),
		strings.Join(formRows, "\n"),
		m.theme.renderSection(width, "Preview", previewLines),
	}
	if m.errMessage != "" {
		bodyParts = append(bodyParts, m.theme.renderMessage(width, "warn", m.errMessage))
	}

	return m.theme.renderFrame(
		m.width,
		m.title,
		m.subtitle,
		strings.Join(bodyParts, "\n\n"),
		"tab move  •  enter next/toggle/save  •  esc cancel",
	)
}

func runCIFSMountForm(in io.Reader, out io.Writer, theme tuiTheme, title, subtitle string, defaults CIFSMountConfig) (CIFSMountConfig, error) {
	model := newCIFSMountFormModel(theme, title, subtitle, defaults)

	result, err := tea.NewProgram(model, tea.WithInput(in), tea.WithOutput(out), tea.WithAltScreen()).Run()
	if err != nil {
		return CIFSMountConfig{}, err
	}

	final := result.(cifsMountFormModel)
	if final.cancel {
		return CIFSMountConfig{}, ErrCancelled
	}
	return final.result, nil
}
