package digitalocean

import (
	"context"
	"errors"
	"fmt"

	"github.com/digitalocean/godo"
	"github.com/rivo/tview"
	"github.com/wtfutil/wtf/utils"
	"github.com/wtfutil/wtf/view"
	"golang.org/x/oauth2"
)

/* -------------------- Oauth2 Token -------------------- */

type tokenSource struct {
	AccessToken string
}

// Token creates and returns an Oauth2 token
func (t *tokenSource) Token() (*oauth2.Token, error) {
	token := &oauth2.Token{
		AccessToken: t.AccessToken,
	}
	return token, nil
}

/* -------------------- Widget -------------------- */

// Widget is the container for droplet data
type Widget struct {
	view.KeyboardWidget
	view.ScrollableWidget

	app      *tview.Application
	client   *godo.Client
	droplets []godo.Droplet
	pages    *tview.Pages
	settings *Settings
	err      error
}

// NewWidget creates a new instance of a widget
func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget {
	widget := Widget{
		KeyboardWidget:   view.NewKeyboardWidget(app, pages, settings.common),
		ScrollableWidget: view.NewScrollableWidget(app, settings.common),

		app:      app,
		pages:    pages,
		settings: settings,
	}

	widget.initializeKeyboardControls()
	widget.View.SetInputCapture(widget.InputCapture)

	widget.View.SetScrollable(true)

	widget.KeyboardWidget.SetView(widget.View)
	widget.SetRenderFunction(widget.display)

	widget.createClient()

	return &widget
}

/* -------------------- Exported Functions -------------------- */

// Fetch retrieves droplet data
func (widget *Widget) Fetch() error {
	if widget.client == nil {
		return errors.New("client could not be initialized")
	}

	var err error
	widget.droplets, err = widget.dropletsFetch()
	return err
}

// HelpText returns the help text for this widget
func (widget *Widget) HelpText() string {
	return widget.KeyboardWidget.HelpText()
}

// Next selects the next item in the list
func (widget *Widget) Next() {
	widget.ScrollableWidget.Next()
}

// Prev selects the previous item in the list
func (widget *Widget) Prev() {
	widget.ScrollableWidget.Prev()
}

// Refresh updates the data for this widget and displays it onscreen
func (widget *Widget) Refresh() {
	err := widget.Fetch()
	if err != nil {
		widget.err = err
		widget.SetItemCount(0)
	} else {
		widget.err = nil
		widget.SetItemCount(len(widget.droplets))
	}

	widget.display()
}

// Unselect clears the selection of list items
func (widget *Widget) Unselect() {
	widget.ScrollableWidget.Unselect()
	widget.RenderFunction()
}

/* -------------------- Unexported Functions -------------------- */

// createClient create a persisten DigitalOcean client for use in the calls below
func (widget *Widget) createClient() {
	tokenSource := &tokenSource{
		AccessToken: widget.settings.apiKey,
	}

	oauthClient := oauth2.NewClient(context.Background(), tokenSource)
	widget.client = godo.NewClient(oauthClient)
}

// currentDroplet returns the currently-selected droplet, if there is one
// Returns nil if no droplet is selected
func (widget *Widget) currentDroplet() *godo.Droplet {
	if len(widget.droplets) == 0 {
		return nil
	}

	if len(widget.droplets) <= widget.Selected {
		return nil
	}

	return &widget.droplets[widget.Selected]
}

// dropletsFetch uses the DigitalOcean API to fetch information about all the available droplets
func (widget *Widget) dropletsFetch() ([]godo.Droplet, error) {
	dropletList := []godo.Droplet{}
	opts := &godo.ListOptions{}

	for {
		droplets, resp, err := widget.client.Droplets.List(context.Background(), opts)
		if err != nil {
			return dropletList, err
		}

		dropletList = append(dropletList, droplets...)

		if resp.Links == nil || resp.Links.IsLastPage() {
			break
		}

		page, err := resp.Links.CurrentPage()
		if err != nil {
			return dropletList, err
		}

		// Set the page we want for the next request
		opts.Page = page + 1
	}

	return dropletList, nil
}

/* -------------------- Droplet Actions -------------------- */

// dropletDestroy destroys the selected droplet
func (widget *Widget) dropletDestroy() {
	currDroplet := widget.currentDroplet()
	if currDroplet == nil {
		return
	}

	_, err := widget.client.Droplets.Delete(context.Background(), currDroplet.ID)
	if err != nil {
		return
	}

	widget.dropletRemoveSelected()
	widget.Refresh()
}

// dropletEnabledPrivateNetworking enabled private networking on the selected droplet
func (widget *Widget) dropletEnabledPrivateNetworking() {
	currDroplet := widget.currentDroplet()
	if currDroplet == nil {
		return
	}

	_, _, err := widget.client.DropletActions.EnablePrivateNetworking(context.Background(), currDroplet.ID)
	if err != nil {
		return
	}

	widget.Refresh()
}

// dropletRemoveSelected removes the currently-selected droplet from the internal list of droplets
func (widget *Widget) dropletRemoveSelected() {
	currDroplet := widget.currentDroplet()
	if currDroplet != nil {
		widget.droplets[len(widget.droplets)-1], widget.droplets[widget.Selected] = widget.droplets[widget.Selected], widget.droplets[len(widget.droplets)-1]
		widget.droplets = widget.droplets[:len(widget.droplets)-1]
	}
}

// dropletRestart restarts the selected droplet
func (widget *Widget) dropletRestart() {
	currDroplet := widget.currentDroplet()
	if currDroplet == nil {
		return
	}

	_, _, err := widget.client.DropletActions.Reboot(context.Background(), currDroplet.ID)
	if err != nil {
		return
	}
	widget.Refresh()
}

// dropletShutDown powers down the selected droplet
func (widget *Widget) dropletShutDown() {
	currDroplet := widget.currentDroplet()
	if currDroplet == nil {
		return
	}

	_, _, err := widget.client.DropletActions.Shutdown(context.Background(), currDroplet.ID)
	if err != nil {
		return
	}
	widget.Refresh()
}

/* -------------------- Common Actions -------------------- */

// showInfo shows a modal window with information about the selected droplet
func (widget *Widget) showInfo() {
	droplet := widget.currentDroplet()
	if droplet == nil {
		return
	}

	closeFunc := func() {
		widget.pages.RemovePage("info")
		widget.app.SetFocus(widget.View)
	}

	propTable := newDropletPropertiesTable(droplet).render()
	propTable += utils.CenterText("Esc to close", 80)

	modal := view.NewBillboardModal(propTable, closeFunc)
	modal.SetTitle(fmt.Sprintf("  %s  ", droplet.Name))

	widget.pages.AddPage("info", modal, false, true)
	widget.app.SetFocus(modal)

	widget.app.QueueUpdateDraw(func() {
		widget.app.Draw()
	})
}
