package loader

import (
	"database/sql"
	"encoding/json"
	"fmt"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/mitchellh/mapstructure"
	"github.com/muesli/termenv"
	"github.com/spf13/viper"
	"io/ioutil"
	"os"
	"pgm/config"
	"pgm/data"
	"pgm/db"
	"pgm/logger"
	"pgm/ui"
	"strconv"
	"strings"
	"time"
)

type host struct {
	tic      int
	t        string
	choices  []string
	choice   int
	chosen   bool
	Quitting bool
	Frames   int
	File     string
	isTest   bool
}

var (
	term   = termenv.ColorProfile()
	subtle = makeFgStyle("241")
	dot    = colorFg(" • ", "236")
)

func Loader(t []string, s string, b string) error {
	var hostChoice host
	var isT bool
	isT = setTest(s)
	logger.Logger("[LOG] is loader test: " + strconv.FormatBool(hostChoice.isTest))
	hostChoice = host{60, s, t, 0, false, false, 0, b, isT}
	if err := tea.NewProgram(hostChoice).Start(); err != nil {
		if isT == true {
			return nil
		}
		logger.Logger("[ERROR] tea error occured")
		os.Exit(1)
	}
	return nil

}

// method to enable go tests if set to true
// loader function will test that it can load the ui to chose a database
// and quits the ui
func setTest(s string) bool {
	logger.Logger("[LOG] is test: " + s)
	if s == "test" {
		return true
	}
	return false
}

func (m host) Init() tea.Cmd {
	return tick()
}

func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {

	case tea.KeyMsg:
		switch msg.String() {
		case "q", "esc":
			m.Quitting = true
			return m, tea.Quit
		case "j", "down":
			m.choice += 1
			if m.choice > len(m.choices) {
				m.choice = len(m.choices)
			}
		case "k", "up":
			m.choice -= 1
			if m.choice < 0 {
				m.choice = 0
			}
		case "l", "enter":
			m.File = m.choices[m.choice]
			m.chosen = true
			return m, Frame()
		}

	case tickMsg:
		if m.isTest == true {
			m.File = "test"
			m.Quitting = true
			return m, tea.Quit
		}
		if m.tic == 0 {
			m.Quitting = true
			return m, tea.Quit
		}
		m.tic -= 1
		return m, tick()
	}
	return m, nil
}

// Update loop for the second view after a choice has been made
func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	if msg, ok := msg.(tea.KeyMsg); ok {
		k := msg.String()
		if k == "q" || k == "esc" || k == "ctrl+c" {
			m.Quitting = true
			return m, tea.Quit
		}
	}

	if !m.chosen {
		return choicesUpdate(msg, m)
	}
	return m, tea.Quit
}

// Views return a string based on data in the host. That string which will be
// rendered to the terminal.
func (m host) View() string {
	logger.Logger("[LOG] view initialized")
	var s string
	if m.Quitting {
		logger.Logger("[LOG] leaving pgm")
		return "leaving pgm..."

	}
	if !m.chosen {
		s = choicesView(m)
	} else {
		tea.Quit()
		LoadUi(m)
	}
	return s
}

// Passes our config to the screen file.
// screen is tasked with rendering more in depth views.
func LoadUi(m host) {
	var g data.HostDetails
	var h string
	h, err := os.UserHomeDir()
	if err != nil {
		logger.Logger("[ERROR] uable to set home dir: " + err.Error())
		os.Exit(0)
	}
	config.ReadConfig()
	l := viper.GetString("hosts_dir")
	filepath := h +  "/" + l + "/" + m.File
	loadhost(filepath, &g)
	Screener(g)

}

// Creates the menu for choosing a file from your configs.
// Exits after 60 seconds.
// Gets Title from outside in the load method if this... should ever be needed elsewhere.
func choicesView(m host) string {
	var menu string
	c := m.choice

	tpl := m.t + "\n\n"
	tpl += "%s\n\n"
	tpl += "menu exits in %s seconds\n\n"
	tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit")
	for i, s := range m.choices {
		menu = menu + "::" + checkbox(s, c == i)
	}
	mn := strings.Replace(menu, "::", "\n", -1)
	choices := fmt.Sprintf(
		mn,
	)

	return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79"))
}

// the rest of these functions are to do with looks and aesthetics

// returns a neat checkbox
func checkbox(label string, checked bool) string {
	if checked {
		return colorFg("[x] "+label, "212")
	}
	return fmt.Sprintf("[ ] %s", label)
}

// fun colors
func colorFg(val, color string) string {
	return termenv.String(val).Foreground(term.Color(color)).String()
}

// Return a function that will colorize the foreground of a given string.
func makeFgStyle(color string) func(string) string {
	return termenv.Style{}.Foreground(term.Color(color)).Styled
}

// Frame event
func Frame() tea.Cmd {
	return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
		return FrameMsg{}
	})
}

type tickMsg struct{}
type FrameMsg struct{}

// exits if no choice is selected in a minute
func tick() tea.Cmd {
	return tea.Tick(time.Second, func(time.Time) tea.Msg {
		return tickMsg{}
	})
}

// loads a yaml file into the correct format for consumption.
// errs the pipeline step if there is not a configuration in this repository.
func loadhost(c string, p *data.HostDetails) *data.HostDetails {
	logger.Logger("[LOG] file path string is: " + c)
	cfg, err := ioutil.ReadFile(c)
	if err != nil {
		logger.Logger("[ERROR] unable to find this filepath: " + err.Error())
		os.Exit(1)
	}
	json.Unmarshal(cfg, &p)
	return p
}

func Screener(g data.HostDetails) error {
	var scn Scr
	var dbConn *sql.DB
	logger.Logger("[LOG] starting screen")
	s := data.ViperReturnKey("UserSessions")

	mapstructure.Decode(s, &scn)
	dbConn = db.DbConn(g)
	switch g.Username {
	case "test":
		dbConn.Close()
		return nil
	}
	ui.GraphicUi(dbConn)
	return nil
}

// Simple struct for a view in the UI.
type Scr struct {
	Query    string `json:"query"`
	Columns  int    `json:"columns"`
	UIType   string `json:"uiType"`
	Title    string `json:"title"`
	Quitting bool
}

func (m Scr) Init() tea.Cmd {
	return tick()
}

func (m Scr) View() string {

	return m.Query
}

func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	if msg, ok := msg.(tea.KeyMsg); ok {
		k := msg.String()
		if k == "q" || k == "esc" || k == "ctrl+c" {
			m.Quitting = true
			return m, tea.Quit
		}
	}
	return m, nil
}
