// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package outputs

import (
	"fmt"
	"io"
	"strings"

	"github.com/charmbracelet/bubbles/key"
	"github.com/charmbracelet/bubbles/list"
	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"

	"github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
	"github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter"
)

const (
	// formatting.
	sortDirectionPadding = 2
	sortingTitlePadding  = 3
	sortingFooterPadding = 2

	// controls.
	sortingListControls = "Controls: ↑/↓ - up/down • enter - select filter • tab - toggle direction • esc - return to table • q - quit"
	sortingTextControls = "Controls: ↑/↓ - up/down • tab - toggle direction • enter - enter json path"

	// sort direction text.
	ascendingText  = "ASCENDING"
	descendingText = "DESCENDING"
)

// sortingModel holds the state for the sorting view.
type sortingModel struct {
	// list which holds the available shorting shorthands
	shorthandList list.Model

	// text input for json paths
	sortTextInput textinput.Model

	instanceTypes []*instancetypes.Details

	isDescending bool
}

// format styles.
var (
	// list.
	listTitleStyle    = lipgloss.NewStyle().Bold(true).Underline(true)
	listItemStyle     = lipgloss.NewStyle().PaddingLeft(4)
	selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))

	// text.
	descendingStyle    = lipgloss.NewStyle().Foreground(lipgloss.Color("#0096FF"))
	ascendingStyle     = lipgloss.NewStyle().Foreground(lipgloss.Color("#DAF7A6"))
	sortDirectionStyle = lipgloss.NewStyle().Bold(true).Underline(true).PaddingLeft(2)
)

// implement Item interface for list.
type item string

func (i item) FilterValue() string { return "" }
func (i item) Title() string       { return string(i) }
func (i item) Description() string { return "" }

// implement ItemDelegate for list.
type itemDelegate struct{}

func (d itemDelegate) Height() int                             { return 1 }
func (d itemDelegate) Spacing() int                            { return 0 }
func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
	i, ok := listItem.(item)
	if !ok {
		return
	}

	str := fmt.Sprintf("%d. %s", index+1, i)

	fn := listItemStyle.Render
	if index == m.Index() {
		fn = func(s ...string) string {
			t := make([]string, 0, len(s)+1)
			t = append(t, "> ")
			t = append(t, s...)
			return selectedItemStyle.Render(t...)
		}
	}

	fmt.Fprint(w, fn(str))
}

// initSortingModel initializes and returns a new tableModel based on the given
// instance type details.
func initSortingModel(instanceTypes []*instancetypes.Details) *sortingModel {
	shorthandList := list.New(*createListItems(), itemDelegate{}, initialDimensionVal, initialDimensionVal)
	shorthandList.Title = "Select sorting filter:"
	shorthandList.Styles.Title = listTitleStyle
	shorthandList.SetFilteringEnabled(false)
	shorthandList.SetShowStatusBar(false)
	shorthandList.SetShowHelp(false)
	shorthandList.SetShowPagination(false)
	shorthandList.KeyMap = createListKeyMap()

	sortTextInput := textinput.New()
	sortTextInput.Prompt = "JSON Path: "
	sortTextInput.PromptStyle = lipgloss.NewStyle().Bold(true)

	return &sortingModel{
		shorthandList: shorthandList,
		sortTextInput: sortTextInput,
		instanceTypes: instanceTypes,
		isDescending:  false,
	}
}

// createListKeyMap creates a KeyMap with the controls for the shorthand list.
func createListKeyMap() list.KeyMap {
	return list.KeyMap{
		CursorDown: key.NewBinding(
			key.WithKeys("down"),
		),
		CursorUp: key.NewBinding(
			key.WithKeys("up"),
		),
	}
}

// createListItems creates a list item for shorthand sorting flag.
func createListItems() *[]list.Item {
	shorthandFlags := []string{
		sorter.GPUCountField,
		sorter.InferenceAcceleratorsField,
		sorter.VCPUs,
		sorter.Memory,
		sorter.GPUMemoryTotal,
		sorter.NetworkInterfaces,
		sorter.SpotPrice,
		sorter.ODPrice,
		sorter.InstanceStorage,
		sorter.EBSOptimizedBaselineBandwidth,
		sorter.EBSOptimizedBaselineThroughput,
		sorter.EBSOptimizedBaselineIOPS,
	}

	items := []list.Item{}

	for _, flag := range shorthandFlags {
		items = append(items, item(flag))
	}

	return &items
}

// resizeSortingView will change the dimensions of the sorting view
// in order to accommodate the new window dimensions represented by
// the given tea.WindowSizeMsg.
func (m sortingModel) resizeView(msg tea.WindowSizeMsg) sortingModel {
	shorthandList := &m.shorthandList
	shorthandList.SetWidth(msg.Width)
	// ensure that text input is right below last option
	if msg.Height >= len(shorthandList.Items())+sortingTitlePadding+sortingFooterPadding {
		shorthandList.SetHeight(len(shorthandList.Items()) + sortingTitlePadding)
	} else if msg.Height-sortingFooterPadding-sortDirectionPadding > 0 {
		shorthandList.SetHeight(msg.Height - sortingFooterPadding - sortDirectionPadding)
	} else {
		shorthandList.SetHeight(1)
	}

	// ensure cursor of list is still hidden after resize
	if m.sortTextInput.Focused() {
		shorthandList.Select(len(m.shorthandList.Items()))
	}

	m.shorthandList = *shorthandList

	return m
}

// update updates the state of the sortingModel.
func (m sortingModel) update(msg tea.Msg) (sortingModel, tea.Cmd) {
	var cmd tea.Cmd
	var cmds []tea.Cmd

	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "down":
			if m.shorthandList.Index() == len(m.shorthandList.Items())-1 {
				// focus text input and hide cursor in shorthand list
				m.shorthandList.Select(len(m.shorthandList.Items()))
				m.sortTextInput.Focus()
			}
		case "up":
			if m.sortTextInput.Focused() {
				// go back to list from text input
				m.shorthandList.Select(len(m.shorthandList.Items()))
				m.sortTextInput.Blur()
			}
		case "tab":
			m.isDescending = !m.isDescending
		}

		if m.sortTextInput.Focused() {
			m.sortTextInput, cmd = m.sortTextInput.Update(msg)
			cmds = append(cmds, cmd)
		}
	}

	if !m.sortTextInput.Focused() {
		m.shorthandList, cmd = m.shorthandList.Update(msg)
		cmds = append(cmds, cmd)
	}

	return m, tea.Batch(cmds...)
}

// view returns a string representing the sorting view.
func (m sortingModel) view() string {
	outputStr := strings.Builder{}

	// draw sort direction
	outputStr.WriteString(sortDirectionStyle.Render("Sort Direction:"))
	outputStr.WriteString(" ")
	if m.isDescending {
		outputStr.WriteString(descendingStyle.Render(descendingText))
	} else {
		outputStr.WriteString(ascendingStyle.Render(ascendingText))
	}
	outputStr.WriteString("\n\n")

	// draw list
	outputStr.WriteString(m.shorthandList.View())
	outputStr.WriteString("\n")

	// draw text input
	outputStr.WriteString(m.sortTextInput.View())
	outputStr.WriteString("\n")

	// draw controls
	if m.sortTextInput.Focused() {
		outputStr.WriteString(controlsStyle.Render(sortingTextControls))
	} else {
		outputStr.WriteString(controlsStyle.Render(sortingListControls))
	}

	return outputStr.String()
}
