package v1shell

import (
	"fmt"
	"os"
	"strings"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/evertras/bubble-table/table"
	"github.com/fatih/color"
	"github.com/influxdata/influx-cli/v2/api"
	"github.com/mattn/go-isatty"
)

const (
	colPadding int = 2
)

type EndingStatus uint

const (
	quitStatus EndingStatus = iota
	goToPrevTableStatus
	goToPrevTableJumpFirstPageStatus
	goToNextTableStatus
)

type Model struct {
	name               string
	tags               map[string]string
	curResult          int
	resultMax          int
	curSeries          int
	seriesMax          int
	rows               []table.Row
	allCols            []table.Column
	colNames           []string
	simpleTable        table.Model
	totalWidth         int
	endingStatus       EndingStatus
	tableScreenPadding int
}

func NewModel(
	res api.InfluxqlJsonResponseSeries,
	jumpToFirstPage bool,
	name string,
	tags map[string]string,
	curRes int,
	resMax int,
	curSer int,
	serMax int,
	scientific bool) Model {

	oneColsLen := len(res.GetColumns()) + 1
	cols := make([]table.Column, oneColsLen)
	colWidths := make([]int, oneColsLen)
	alignment := make([]lipgloss.Position, oneColsLen)
	rows := make([]table.Row, len(res.GetValues()))
	colNames := res.GetColumns()
	for rowI, row := range res.GetValues() {
		rd := table.RowData{}
		rd["index"] = fmt.Sprintf("%d", rowI+1)
		alignment[0] = lipgloss.Right
		colWidths[0] = len("index") + colPadding
		for colI, rowVal := range row {
			var item string
			var colLen int
			switch val := rowVal.(type) {
			case int:
				item = fmt.Sprintf("%d", val)
				colLen = len(item)
				alignment[colI+1] = lipgloss.Right
			case string:
				item = color.YellowString(val)
				colLen = len(val)
				alignment[colI+1] = lipgloss.Left
			case float32, float64:
				if scientific {
					item = fmt.Sprintf("%.10e", val)
				} else {
					item = fmt.Sprintf("%.10f", val)
				}
				colLen = len(item)
				alignment[colI+1] = lipgloss.Right
			default:
				item = fmt.Sprintf("%v", val)
				colLen = len(item)
				alignment[colI+1] = lipgloss.Right
			}
			rd[colNames[colI]] = item
			if colWidths[colI+1] < colLen+colPadding {
				colWidths[colI+1] = colLen + colPadding
			}
		}
		rows[rowI] = table.NewRow(rd).
			WithStyle(lipgloss.NewStyle())
	}
	cols[0] = table.NewColumn("index", "index", colWidths[0])
	indexStyle := lipgloss.NewStyle()
	if isatty.IsTerminal(os.Stdout.Fd()) {
		indexStyle = indexStyle.
			Faint(true).
			Align(lipgloss.Right)
	}
	cols[0] = cols[0].WithStyle(indexStyle)
	for colI, colTitle := range colNames {
		if colWidths[colI+1] < len(colTitle)+colPadding {
			colWidths[colI+1] = len(colTitle) + colPadding
		}
		cols[colI+1] = table.NewColumn(colTitle, color.HiCyanString(colTitle), colWidths[colI+1]).
			WithStyle(lipgloss.NewStyle().Align(alignment[colI+1]))
	}
	colNames = append([]string{"index"}, colNames...)
	screenPadding := 10
	if len(tags) > 0 {
		screenPadding++
	}
	m := Model{
		name:               name,
		tags:               tags,
		curResult:          curRes,
		resultMax:          resMax,
		curSeries:          curSer,
		seriesMax:          serMax,
		rows:               rows,
		allCols:            cols,
		colNames:           colNames,
		tableScreenPadding: screenPadding,
	}
	keybind := table.DefaultKeyMap()
	keybind.RowUp.SetEnabled(false)
	keybind.RowDown.SetEnabled(false)
	keybind.PageUp.SetEnabled(false)
	keybind.PageDown.SetEnabled(false)
	keybind.ScrollLeft.SetKeys("left")
	keybind.ScrollRight.SetKeys("right")
	keybind.Filter.Unbind()
	keybind.FilterBlur.Unbind()
	keybind.FilterClear.Unbind()

	m.simpleTable = table.New(m.allCols).
		HeaderStyle(lipgloss.NewStyle().Align(lipgloss.Center)).
		WithRows(m.rows).
		WithPageSize(15).
		WithMaxTotalWidth(500).
		WithHorizontalFreezeColumnCount(1).
		WithStaticFooter(
			fmt.Sprintf("%d Columns, %d Rows, Page %d/%d",
				len(m.allCols), len(m.rows), m.simpleTable.CurrentPage(), m.simpleTable.MaxPages())).
		HighlightStyle(lipgloss.NewStyle()).
		Focused(true).
		SelectableRows(false).
		WithKeyMap(keybind)

	if jumpToFirstPage {
		m.simpleTable = m.simpleTable.PageLast()
	}
	return m
}

func (m Model) Init() tea.Cmd {
	color.Magenta("Interactive Table View (press q to exit mode, shift+up/down to navigate tables):")
	builder := strings.Builder{}
	if m.name != "" {
		fmt.Printf("Name: %s\n", color.GreenString(m.name))
	} else {
		fmt.Println("") // keep a consistent height, so print an empty line
	}
	if len(m.tags) > 0 {
		fmt.Print("Tags: ")
		for key, val := range m.tags {
			if key == "" || val == "" {
				continue
			}
			builder.WriteString(fmt.Sprintf("%s=%s, ", color.YellowString(key), color.CyanString(val)))
		}
		tagline := builder.String()
		fmt.Print(tagline[:len(tagline)-2])
		fmt.Println("")
	}
	return nil
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var (
		cmd  tea.Cmd
		cmds []tea.Cmd
	)

	m.simpleTable, cmd = m.simpleTable.Update(msg)
	cmds = append(cmds, cmd)

	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "ctrl+d", "esc", "q":
			m.simpleTable = m.simpleTable.Focused(false)
			fmt.Printf("\n")
			m.endingStatus = quitStatus
			cmds = append(cmds, tea.Quit)
		case "shift+up":
			if !(m.curResult == 1 && m.curSeries == 1) {
				m.endingStatus = goToPrevTableJumpFirstPageStatus
				cmds = append(cmds, tea.Quit)
			}
		case "shift+down":
			if !(m.curResult == m.resultMax && m.curSeries == m.seriesMax) {
				m.endingStatus = goToNextTableStatus
				cmds = append(cmds, tea.Quit)
			}
		case "up":
			if m.simpleTable.CurrentPage() == 1 {
				if !(m.curResult == 1 && m.curSeries == 1) {
					m.endingStatus = goToPrevTableStatus
					cmds = append(cmds, tea.Quit)
				}
			} else {
				m.simpleTable = m.simpleTable.PageUp()
			}
		case "down":
			if m.simpleTable.CurrentPage() == m.simpleTable.MaxPages() {
				if !(m.curResult == m.resultMax && m.curSeries == m.seriesMax) {
					m.endingStatus = goToNextTableStatus
					cmds = append(cmds, tea.Quit)
				}
			} else {
				m.simpleTable = m.simpleTable.PageDown()
			}
		case "[":
			m.simpleTable = m.simpleTable.PageFirst()
		case "]":
			m.simpleTable = m.simpleTable.PageLast()
		}
	case tea.WindowSizeMsg:
		m.totalWidth = msg.Width
		m.simpleTable = m.simpleTable.WithPageSize(msg.Height - m.tableScreenPadding).
			WithMaxTotalWidth(msg.Width)
	}
	m.simpleTable = m.simpleTable.WithStaticFooter(
		fmt.Sprintf("%d Columns, %d Rows, Page %d/%d\nTable %d/%d, Statement %d/%d",
			len(m.allCols), len(m.rows), m.simpleTable.CurrentPage(), m.simpleTable.MaxPages(),
			m.curSeries, m.seriesMax,
			m.curResult, m.resultMax))
	return m, tea.Batch(cmds...)
}

func (m Model) View() string {
	body := strings.Builder{}

	body.WriteString(m.simpleTable.View())
	body.WriteString("\n")
	return body.String()
}
