// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 tui

import (
	"context"
	"fmt"
	"time"

	"github.com/aws/amazon-ec2-spot-interrupter/pkg/itn"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	"github.com/charmbracelet/bubbles/spinner"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/samber/lo"
)

var helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render

type model struct {
	choices     []ec2types.Instance
	cursor      int
	selected    map[int]*ec2types.Instance
	ctx         context.Context
	itn         *itn.ITN
	initialized bool
	spinner     spinner.Model
}

type spotInstancesMsg []ec2types.Instance
type retrySpotInstances time.Time

func NewModel(ctx context.Context, itn *itn.ITN) model {
	sp := spinner.New()
	sp.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("206"))
	sp.Spinner = spinner.Points
	return model{
		selected: map[int]*ec2types.Instance{},
		ctx:      ctx,
		itn:      itn,
		spinner:  sp,
	}
}

func initialModel(ctx context.Context, itn *itn.ITN) tea.Cmd {
	return func() tea.Msg {
		instances, err := itn.SpotInstances(ctx)
		if err != nil {
			panic(err)
		}
		return spotInstancesMsg(instances)
	}
}

func (m model) Init() tea.Cmd {
	return tea.Batch(
		spinner.Tick,
		initialModel(m.ctx, m.itn),
	)
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case spotInstancesMsg:
		m.choices = msg
		m.initialized = true
		if len(msg) == 0 {
			return m, tea.Every(time.Second*15, func(t time.Time) tea.Msg {
				return retrySpotInstances(t)
			})
		}
	case retrySpotInstances:
		return m, initialModel(m.ctx, m.itn)
	case spinner.TickMsg:
		var cmd tea.Cmd
		m.spinner, cmd = m.spinner.Update(msg)
		return m, cmd
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "q":
			return m, tea.Quit
		case "up", "k":
			if m.cursor > 0 {
				m.cursor--
			}
		case "down", "j":
			if m.cursor < len(m.choices)-1 {
				m.cursor++
			}
		case " ":
			if _, ok := m.selected[m.cursor]; ok {
				delete(m.selected, m.cursor)
			} else {
				m.selected[m.cursor] = &m.choices[m.cursor]
			}
		case "enter":
			opts := NewOptions(m.ctx, m.itn, lo.Values(m.selected))
			return opts, opts.Init()
		}
	}
	return m, nil
}

func instanceName(i ec2types.Instance) string {
	for _, tag := range i.Tags {
		if *tag.Key == "Name" {
			return *tag.Value
		}
	}
	return ""
}

func (m model) View() string {
	if !m.initialized {
		return fmt.Sprintf("Finding Spot instances %s\n%s", m.spinner.View(), help())
	}
	if len(m.choices) == 0 {
		return fmt.Sprintf("There are currently no Spot instances running...\nI'll keep checking though %s\n%s", m.spinner.View(), help())
	}
	// The header
	s := "Which Spot instances would you like to interrupt?\n\n"

	// Iterate over our choices
	for i, choice := range m.choices {

		// Is the cursor pointing at this choice?
		cursor := " " // no cursor
		if m.cursor == i {
			cursor = ">" // cursor!
		}

		// Is this choice selected?
		checked := " " // not selected
		if _, ok := m.selected[i]; ok {
			checked = "x" // selected!
		}

		// Render the row
		s += fmt.Sprintf("%s [%s] %s (%s)\n", cursor, checked, *choice.InstanceId, instanceName(choice))
	}

	// The footer
	s += help()

	// Send the UI for rendering
	return s
}

func help() string {
	return helpStyle("\nPress q to quit.\n")
}
