package importlist

import (
	"fmt"
	"strings"

	"github.com/Azure/aztfexport/internal/tfaddr"

	"github.com/Azure/aztfexport/internal/ui/aztfexportclient"
	"github.com/Azure/aztfexport/internal/ui/common"
	"github.com/magodo/tfadd/providers/azapi"
	"github.com/magodo/tfadd/providers/azurerm"

	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"
)

func NewImportItemDelegate(providerName string) list.ItemDelegate {
	d := list.NewDefaultDelegate()
	d.UpdateFunc = func(msg tea.Msg, m *list.Model) (ret tea.Cmd) {
		sel := m.SelectedItem()
		if sel == nil {
			return nil
		}
		selItem := sel.(Item)
		idx := selItem.idx

		var cmds []tea.Cmd
		defer func() {
			cmd := m.SetItem(idx, selItem)
			cmds = append(cmds, cmd)
			ret = tea.Batch(cmds...)
		}()

		// For the item that is not focused (i.e. the textinput is not focused)
		if !selItem.textinput.Focused() {
			switch msg := msg.(type) {
			case tea.KeyMsg:
				switch {
				case msg.Type == tea.KeyEnter:
					// Clear the validation error that were set.
					selItem.v.ValidateError = nil

					// Clear the imported flag that were set, which means this resource will be imported again.
					// This allows the user to change its mind for importing this resource as another resource type.
					// (e.g. vm resource -> either azurerm_virtual_machine or azurerm_linux_virtual_machine)
					if selItem.v.Imported {
						cmd := aztfexportclient.CleanTFState(selItem.v.TFAddr.String())
						cmds = append(cmds, cmd)
						selItem.v.Imported = false
					}

					// Clear the is recommended flag that were set.
					selItem.v.IsRecommended = false

					// "Enter" focus current selected item
					setListKeyMapEnabled(m, false)
					cmd := selItem.textinput.Focus()
					cmds = append(cmds, cmd)
					return
				}
			}
			return
		}

		// The item is focused.
		switch msg := msg.(type) {
		case tea.KeyMsg:
			switch msg.Type {
			case tea.KeyEnter,
				tea.KeyEsc:
				// Enter and ESC un-focus the textinput
				setListKeyMapEnabled(m, true)
				selItem.textinput.Blur()

				// Validate the input and update the selItem.v
				addr, err := parseInput(selItem.textinput.Value(), providerName)
				if err != nil {
					cmd := m.NewStatusMessage(common.ErrorMsgStyle.Render(err.Error()))
					cmds = append(cmds, cmd)
					selItem.v.ValidateError = err
					return
				}

				// Check the uniqueness of the resource name among the resource type
				// TODO: this is not ideal to construct the resource name mapping everytime.
				tfNames := map[string]map[string]bool{}
				for i, item := range m.Items() {
					v := item.(Item).v
					if v.Skip() || m.Index() == i {
						continue
					}
					if _, ok := tfNames[v.TFAddr.Type]; !ok {
						tfNames[v.TFAddr.Type] = map[string]bool{}
					}
					tfNames[v.TFAddr.Type][v.TFAddr.Name] = true
				}
				if mm, ok := tfNames[addr.Type]; ok && mm[addr.Name] {
					err := fmt.Errorf("%q already exists", addr)
					cmd := m.NewStatusMessage(common.ErrorMsgStyle.Render(err.Error()))
					cmds = append(cmds, cmd)
					selItem.v.ValidateError = err
					return
				}

				selItem.v.ValidateError = nil
				selItem.v.TFAddr = *addr
				selItem.v.TFAddrCache = *addr
				return
			}
		}

		var cmd tea.Cmd
		selItem.textinput, cmd = selItem.textinput.Update(msg)
		cmds = append(cmds, cmd)
		return
	}
	return d
}

func setListKeyMapEnabled(m *list.Model, enabled bool) {
	m.KeyMap.CursorUp.SetEnabled(enabled)
	m.KeyMap.CursorDown.SetEnabled(enabled)
	m.KeyMap.NextPage.SetEnabled(enabled)
	m.KeyMap.PrevPage.SetEnabled(enabled)
	m.KeyMap.GoToStart.SetEnabled(enabled)
	m.KeyMap.GoToEnd.SetEnabled(enabled)

	m.KeyMap.Filter.SetEnabled(enabled)
	if enabled {
		m.KeyMap.ClearFilter.SetEnabled(m.FilterState() == list.FilterApplied)
	} else {
		m.KeyMap.ClearFilter.SetEnabled(enabled)
	}

	// m.KeyMap.CancelWhileFiltering.SetEnabled(enabled)
	// m.KeyMap.AcceptWhileFiltering.SetEnabled(enabled)

	m.KeyMap.ShowFullHelp.SetEnabled(enabled)
	m.KeyMap.CloseFullHelp.SetEnabled(enabled)

	m.KeyMap.Quit.SetEnabled(enabled)

	if enabled {
		bindKeyHelps(m, newListKeyMap().ToBindings())
	} else {
		bindKeyHelps(m, nil)
	}
}

func parseInput(input string, providerName string) (*tfaddr.TFAddr, error) {
	v := strings.TrimSpace(input)
	if v == "" {
		return &tfaddr.TFAddr{}, nil
	}

	addr, err := tfaddr.ParseTFResourceAddr(v)
	if err != nil {
		return nil, err
	}

	var ok bool
	switch providerName {
	case "azurerm":
		_, ok = azurerm.ProviderSchemaInfo.ResourceSchemas[addr.Type]
	case "azapi":
		_, ok = azapi.ProviderSchemaInfo.ResourceSchemas[addr.Type]
	}
	if !ok {
		return nil, fmt.Errorf("Invalid resource type %q", addr.Type)
	}

	return addr, nil
}
