package feedreader

import (
	"fmt"
	"html"
	"regexp"
	"sort"
	"strings"

	"github.com/mmcdole/gofeed"
	"github.com/rivo/tview"
	"github.com/wtfutil/wtf/utils"
	"github.com/wtfutil/wtf/view"
	"jaytaylor.com/html2text"
)

type ShowType int

const (
	SHOW_TITLE ShowType = iota
	SHOW_LINK
	SHOW_CONTENT
)

// FeedItem represents an item returned from an RSS or Atom feed
type FeedItem struct {
	item        *gofeed.Item
	sourceTitle string
	viewed      bool
}

// Widget is the container for RSS and Atom data
type Widget struct {
	view.ScrollableWidget

	stories  []*FeedItem
	parser   *gofeed.Parser
	settings *Settings
	err      error
	showType ShowType
}

func rotateShowType(showtype ShowType) ShowType {
	returnValue := SHOW_TITLE
	switch showtype {
	case SHOW_TITLE:
		returnValue = SHOW_LINK
	case SHOW_LINK:
		returnValue = SHOW_CONTENT
	case SHOW_CONTENT:
		returnValue = SHOW_TITLE
	}
	return returnValue
}

func getShowText(feedItem *FeedItem, showType ShowType) string {
	if feedItem == nil {
		return ""
	}

	space := regexp.MustCompile(`\s+`)
	title := space.ReplaceAllString(feedItem.item.Title, " ")
	if feedItem.sourceTitle != "" {
		title = "[" + feedItem.sourceTitle + "] " + space.ReplaceAllString(feedItem.item.Title, " ")
	}

	// Convert any escaped characters to their character representation
	title = html.UnescapeString(title)

	switch showType {
	case SHOW_LINK:
		return feedItem.item.Link
	case SHOW_CONTENT:
		text, _ := html2text.FromString(feedItem.item.Content, html2text.Options{PrettyTables: true})
		return strings.TrimSpace(title + "\n" + strings.TrimSpace(text))
	default:
		return title
	}
}

// NewWidget creates a new instance of a widget
func NewWidget(tviewApp *tview.Application, redrawChan chan bool, pages *tview.Pages, settings *Settings) *Widget {
	widget := &Widget{
		ScrollableWidget: view.NewScrollableWidget(tviewApp, redrawChan, pages, settings.Common),

		parser:   gofeed.NewParser(),
		settings: settings,
		showType: SHOW_TITLE,
	}

	widget.SetRenderFunction(widget.Render)
	widget.initializeKeyboardControls()

	return widget
}

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

// Fetch retrieves RSS and Atom feed data
func (widget *Widget) Fetch(feedURLs []string) ([]*FeedItem, error) {
	data := []*FeedItem{}

	for _, feedURL := range feedURLs {
		feedItems, err := widget.fetchForFeed(feedURL)
		if err != nil {
			return nil, err
		}

		data = append(data, feedItems...)
	}

	data = widget.sort(data)

	return data, nil
}

// Refresh updates the data in the widget
func (widget *Widget) Refresh() {
	feedItems, err := widget.Fetch(widget.settings.feeds)
	if err != nil {
		widget.err = err
		widget.stories = nil
		widget.SetItemCount(0)
	} else {
		widget.err = nil
		widget.stories = feedItems
		widget.SetItemCount(len(feedItems))
	}

	widget.Render()
}

// Render sets up the widget data for redrawing to the screen
func (widget *Widget) Render() {
	widget.Redraw(widget.content)
}

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

func (widget *Widget) fetchForFeed(feedURL string) ([]*FeedItem, error) {
	var (
		feed *gofeed.Feed
		err  error
	)
	if auth, isPrivateRSS := widget.settings.credentials[feedURL]; isPrivateRSS {
		fp := gofeed.NewParser()
		fp.AuthConfig = &gofeed.Auth{
			Username: auth.username,
			Password: auth.password,
		}
		feed, err = fp.ParseURL(feedURL)
	} else {
		feed, err = widget.parser.ParseURL(feedURL)
	}

	if err != nil {
		return nil, err
	}

	feedItems := []*FeedItem{}

	for idx, gofeedItem := range feed.Items {
		if widget.settings.feedLimit >= 1 && idx >= widget.settings.feedLimit {
			// We only want to get the widget.settings.feedLimit latest articles,
			// not all of them. To get all, set feedLimit to < 1
			break
		}

		feedItem := &FeedItem{
			item:        gofeedItem,
			sourceTitle: feed.Title,
			viewed:      false,
		}

		feedItems = append(feedItems, feedItem)
	}

	return feedItems, nil
}

func (widget *Widget) content() (string, string, bool) {
	title := widget.CommonSettings().Title
	if widget.err != nil {
		return title, widget.err.Error(), true
	}
	data := widget.stories
	if len(data) == 0 {
		return title, "No data", false
	}
	var str string

	for idx, feedItem := range data {
		rowColor := widget.RowColor(idx)

		if feedItem.viewed {
			// Grays out viewed items in the list, while preserving background highlighting when selected
			rowColor = "gray"
			if idx == widget.Selected {
				rowColor = fmt.Sprintf("gray:%s", widget.settings.Colors.RowTheme.HighlightedBackground)
			}
		}

		displayText := getShowText(feedItem, widget.showType)

		row := fmt.Sprintf(
			"[%s]%2d. %s[white]",
			rowColor,
			idx+1,
			displayText,
		)

		str += utils.HighlightableHelper(widget.View, row, idx, len(feedItem.item.Title))
	}

	return title, str, false
}

// feedItems are sorted by published date
func (widget *Widget) sort(feedItems []*FeedItem) []*FeedItem {
	sort.Slice(feedItems, func(i, j int) bool {
		return feedItems[i].item.PublishedParsed != nil &&
			feedItems[j].item.PublishedParsed != nil &&
			feedItems[i].item.PublishedParsed.After(*feedItems[j].item.PublishedParsed)
	})

	return feedItems
}

func (widget *Widget) openStory() {
	sel := widget.GetSelected()

	if sel >= 0 && widget.stories != nil && sel < len(widget.stories) {
		story := widget.stories[sel]
		story.viewed = true

		utils.OpenFile(story.item.Link)
	}
}

func (widget *Widget) toggleDisplayText() {
	widget.showType = rotateShowType(widget.showType)
	widget.Render()
}
