package fillermanager

import (
	"seanime/internal/api/filler"
	"seanime/internal/database/db"
	"seanime/internal/hook"
	"seanime/internal/library/anime"
	"seanime/internal/onlinestream"
	"seanime/internal/util"
	"strconv"
	"sync"
	"time"

	"github.com/rs/zerolog"
	lop "github.com/samber/lo/parallel"
)

type (
	Interface interface {
		// RefetchFillerData re-fetches the fillers for the given media IDs
		RefetchFillerData() error
		// HasFillerFetched checks if the fillers for the given media ID have been fetched
		HasFillerFetched(mediaId int) bool
		// FetchAndStoreFillerData fetches the filler data for the given media ID
		FetchAndStoreFillerData(mediaId int, titles []string) error
		// RemoveFillerData removes the filler data for the given media ID
		RemoveFillerData(mediaId int) error
		// IsEpisodeFiller checks if the given episode number is a filler for the given media ID
		IsEpisodeFiller(mediaId int, episodeNumber int) bool
	}

	FillerManager struct {
		db        *db.Database
		logger    *zerolog.Logger
		fillerApi filler.API
	}

	NewFillerManagerOptions struct {
		DB     *db.Database
		Logger *zerolog.Logger
	}
)

func New(opts *NewFillerManagerOptions) *FillerManager {
	return &FillerManager{
		db:        opts.DB,
		logger:    opts.Logger,
		fillerApi: filler.NewAnimeFillerList(opts.Logger),
	}
}

func (fm *FillerManager) RefetchFillerData() error {

	defer util.HandlePanicInModuleThen("library/fillermanager/RefetchFillerData", func() {
		fm.logger.Error().Msg("fillermanager: Failed to re-fetch filler data")
	})

	wg := sync.WaitGroup{}

	fm.logger.Debug().Msg("fillermanager: Re-fetching filler data")

	mediaFillers, err := fm.db.GetCachedMediaFillers()
	if err != nil {
		return err
	}

	for _, mf := range mediaFillers {
		wg.Add(1)
		go func(*db.MediaFillerItem) {
			defer wg.Done()
			// Fetch the db data

			// Fetch the filler data
			fillerData, err := fm.fillerApi.FindFillerData(mf.Slug)
			if err != nil {
				fm.logger.Error().Err(err).Int("mediaId", mf.MediaId).Msg("fillermanager: Failed to fetch filler data")
				return
			}

			// Update the filler data
			mf.FillerEpisodes = fillerData.FillerEpisodes

		}(mf)
	}
	wg.Wait()

	err = fm.db.SaveCachedMediaFillerItems()
	if err != nil {
		return err
	}

	fm.logger.Debug().Msg("fillermanager: Re-fetched filler data")

	return nil
}

func (fm *FillerManager) HasFillerFetched(mediaId int) bool {

	defer util.HandlePanicInModuleThen("library/fillermanager/HasFillerFetched", func() {
	})

	_, ok := fm.db.GetMediaFillerItem(mediaId)
	return ok
}

func (fm *FillerManager) GetFillerEpisodes(mediaId int) ([]string, bool) {

	defer util.HandlePanicInModuleThen("library/fillermanager/GetFillerEpisodes", func() {
	})

	fillerItem, ok := fm.db.GetMediaFillerItem(mediaId)
	if !ok {
		return nil, false
	}

	return fillerItem.FillerEpisodes, true
}

func (fm *FillerManager) FetchAndStoreFillerData(mediaId int, titles []string) error {

	defer util.HandlePanicInModuleThen("library/fillermanager/FetchAndStoreFillerData", func() {
	})

	fm.logger.Debug().Int("mediaId", mediaId).Msg("fillermanager: Fetching filler data")

	res, err := fm.fillerApi.Search(filler.SearchOptions{
		Titles: titles,
	})
	if err != nil {
		return err
	}

	fm.logger.Debug().Int("mediaId", mediaId).Str("slug", res.Slug).Msg("fillermanager: Fetched filler data")

	return fm.fetchAndStoreFillerDataFromSlug(mediaId, res.Slug)
}

func (fm *FillerManager) fetchAndStoreFillerDataFromSlug(mediaId int, slug string) error {

	defer util.HandlePanicInModuleThen("library/fillermanager/FetchAndStoreFillerDataFromSlug", func() {
	})

	fillerData, err := fm.fillerApi.FindFillerData(slug)
	if err != nil {
		return err
	}

	err = fm.db.InsertMediaFiller(
		"animefillerlist",
		mediaId,
		slug,
		time.Now(),
		fillerData.FillerEpisodes,
	)
	if err != nil {
		return err
	}

	return nil
}

func (fm *FillerManager) StoreFillerData(source string, slug string, mediaId int, fillerEpisodes []string) error {

	defer util.HandlePanicInModuleThen("library/fillermanager/StoreFillerDataForMedia", func() {
	})

	return fm.db.InsertMediaFiller(
		source,
		mediaId,
		slug,
		time.Now(),
		fillerEpisodes,
	)
}

func (fm *FillerManager) RemoveFillerData(mediaId int) error {

	defer util.HandlePanicInModuleThen("library/fillermanager/RemoveFillerData", func() {
	})

	fm.logger.Debug().Int("mediaId", mediaId).Msg("fillermanager: Removing filler data")

	return fm.db.DeleteMediaFiller(mediaId)
}

func (fm *FillerManager) IsEpisodeFiller(mediaId int, episodeNumber int) bool {

	defer util.HandlePanicInModuleThen("library/fillermanager/IsEpisodeFiller", func() {
	})

	mediaFillerData, ok := fm.db.GetMediaFillerItem(mediaId)
	if !ok {
		return false
	}

	if len(mediaFillerData.FillerEpisodes) == 0 {
		return false
	}

	for _, ep := range mediaFillerData.FillerEpisodes {
		if ep == strconv.Itoa(episodeNumber) {
			return true
		}
	}

	return false
}

func (fm *FillerManager) HydrateFillerData(e *anime.Entry) {
	if fm == nil {
		return
	}
	if e == nil || e.Media == nil || e.Episodes == nil || len(e.Episodes) == 0 {
		return
	}

	event := &HydrateFillerDataRequestedEvent{
		Entry: e,
	}
	_ = hook.GlobalHookManager.OnHydrateFillerDataRequested().Trigger(event)
	if event.DefaultPrevented {
		return
	}
	e = event.Entry

	// Check if the filler data has been fetched
	if !fm.HasFillerFetched(e.Media.ID) {
		return
	}

	lop.ForEach(e.Episodes, func(ep *anime.Episode, _ int) {
		if ep == nil || ep.EpisodeMetadata == nil {
			return
		}
		ep.EpisodeMetadata.IsFiller = fm.IsEpisodeFiller(e.Media.ID, ep.EpisodeNumber)
	})
}

func (fm *FillerManager) HydrateOnlinestreamFillerData(mId int, episodes []*onlinestream.Episode) {
	if fm == nil {
		return
	}
	if episodes == nil || len(episodes) == 0 {
		return
	}

	event := &HydrateOnlinestreamFillerDataRequestedEvent{
		Episodes: episodes,
	}
	_ = hook.GlobalHookManager.OnHydrateOnlinestreamFillerDataRequested().Trigger(event)
	if event.DefaultPrevented {
		return
	}
	episodes = event.Episodes

	// Check if the filler data has been fetched
	if !fm.HasFillerFetched(mId) {
		return
	}

	for _, ep := range episodes {
		ep.IsFiller = fm.IsEpisodeFiller(mId, ep.Number)
	}
}

func (fm *FillerManager) HydrateEpisodeFillerData(mId int, episodes []*anime.Episode) {
	if fm == nil || len(episodes) == 0 {
		return
	}

	event := &HydrateEpisodeFillerDataRequestedEvent{
		Episodes: episodes,
	}
	_ = hook.GlobalHookManager.OnHydrateEpisodeFillerDataRequested().Trigger(event)
	if event.DefaultPrevented {
		return
	}
	episodes = event.Episodes

	// Check if the filler data has been fetched
	if !fm.HasFillerFetched(mId) {
		return
	}

	lop.ForEach(episodes, func(e *anime.Episode, _ int) {
		//h.App.FillerManager.HydrateEpisodeFillerData(mId, e)
		e.EpisodeMetadata.IsFiller = fm.IsEpisodeFiller(mId, e.EpisodeNumber)
	})
}
