package plugin

import (
	"context"
	"errors"
	"seanime/internal/extension"
	"seanime/internal/goja/goja_bindings"
	"seanime/internal/manga"
	goja_util "seanime/internal/util/goja"

	"github.com/dop251/goja"
	"github.com/rs/zerolog"
)

type Manga struct {
	ctx       *AppContextImpl
	vm        *goja.Runtime
	logger    *zerolog.Logger
	ext       *extension.Extension
	scheduler *goja_util.Scheduler
}

func (a *AppContextImpl) BindMangaToContextObj(vm *goja.Runtime, obj *goja.Object, logger *zerolog.Logger, ext *extension.Extension, scheduler *goja_util.Scheduler) {
	m := &Manga{
		ctx:       a,
		vm:        vm,
		logger:    logger,
		ext:       ext,
		scheduler: scheduler,
	}

	mangaObj := vm.NewObject()

	// Get downloaded chapter containers
	_ = mangaObj.Set("getDownloadedChapters", m.getDownloadedChapterContainers)
	_ = mangaObj.Set("getCollection", m.getCollection)
	_ = mangaObj.Set("refreshChapters", m.refreshChapterContainers)
	_ = mangaObj.Set("emptyCache", m.emptyCache)
	_ = mangaObj.Set("getChapterContainer", m.getChapterContainer)
	_ = mangaObj.Set("getProviders", m.getProviders)
	_ = obj.Set("manga", mangaObj)
}

func (m *Manga) getProviders() (map[string]string, error) {
	mangaRepo, ok := m.ctx.mangaRepository.Get()
	if !ok {
		return nil, errors.New("manga repository not found")
	}
	providers := make(map[string]string)
	extension.RangeExtensions(mangaRepo.GetProviderExtensionBank(), func(id string, ext extension.MangaProviderExtension) bool {
		providers[id] = ext.GetName()
		return true
	})
	return providers, nil
}

type GetChapterContainerOptions struct {
	MediaId  int
	Provider string
	Titles   []*string
	Year     int
}

func (m *Manga) getChapterContainer(opts *GetChapterContainerOptions) goja.Value {
	promise, resolve, reject := m.vm.NewPromise()

	mangaRepo, ok := m.ctx.mangaRepository.Get()
	if !ok {
		// reject(goja_bindings.NewErrorString(m.vm, "manga repository not set"))
		// return m.vm.ToValue(promise)
		goja_bindings.PanicThrowErrorString(m.vm, "manga repository not set")
	}

	go func() {
		ret, err := mangaRepo.GetMangaChapterContainer(&manga.GetMangaChapterContainerOptions{
			MediaId:  opts.MediaId,
			Provider: opts.Provider,
			Titles:   opts.Titles,
			Year:     opts.Year,
		})
		m.scheduler.ScheduleAsync(func() error {
			if err != nil {
				reject(err.Error())
			} else {
				resolve(ret)
			}
			return nil
		})
	}()

	return m.vm.ToValue(promise)
}

func (m *Manga) getDownloadedChapterContainers() ([]*manga.ChapterContainer, error) {
	mangaRepo, ok := m.ctx.mangaRepository.Get()
	if !ok {
		return nil, errors.New("manga repository not found")
	}
	anilistPlatform, foundAnilistPlatform := m.ctx.anilistPlatform.Get()
	if !foundAnilistPlatform {
		return nil, errors.New("anilist platform not found")
	}

	mangaCollection, err := anilistPlatform.GetMangaCollection(context.Background(), false)
	if err != nil {
		return nil, err
	}
	return mangaRepo.GetDownloadedChapterContainers(mangaCollection)
}

func (m *Manga) getCollection() (*manga.Collection, error) {
	anilistPlatform, foundAnilistPlatform := m.ctx.anilistPlatform.Get()
	if !foundAnilistPlatform {
		return nil, errors.New("anilist platform not found")
	}

	mangaCollection, err := anilistPlatform.GetMangaCollection(context.Background(), false)
	if err != nil {
		return nil, err
	}
	return manga.NewCollection(&manga.NewCollectionOptions{
		MangaCollection: mangaCollection,
		Platform:        anilistPlatform,
	})
}

func (m *Manga) refreshChapterContainers(selectedProviderMap map[int]string) goja.Value {
	promise, resolve, reject := m.vm.NewPromise()

	mangaRepo, ok := m.ctx.mangaRepository.Get()
	if !ok {
		jsErr := m.vm.NewGoError(errors.New("manga repository not found"))
		_ = reject(jsErr)
		return m.vm.ToValue(promise)
	}
	anilistPlatform, foundAnilistPlatform := m.ctx.anilistPlatform.Get()
	if !foundAnilistPlatform {
		jsErr := m.vm.NewGoError(errors.New("anilist platform not found"))
		_ = reject(jsErr)
		return m.vm.ToValue(promise)
	}

	mangaCollection, err := anilistPlatform.GetMangaCollection(context.Background(), false)
	if err != nil {
		reject(err.Error())
		return m.vm.ToValue(promise)
	}

	go func() {
		err := mangaRepo.RefreshChapterContainers(mangaCollection, selectedProviderMap)
		m.scheduler.ScheduleAsync(func() error {
			if err != nil {
				reject(err.Error())
			} else {
				resolve(nil)
			}
			return nil
		})
	}()

	return m.vm.ToValue(promise)
}

func (m *Manga) emptyCache(mediaId int) goja.Value {
	promise, resolve, reject := m.vm.NewPromise()

	mangaRepo, ok := m.ctx.mangaRepository.Get()
	if !ok {
		// reject(goja_bindings.NewErrorString(m.vm, "manga repository not found"))
		// return m.vm.ToValue(promise)
		goja_bindings.PanicThrowErrorString(m.vm, "manga repository not found")
	}

	go func() {
		err := mangaRepo.EmptyMangaCache(mediaId)
		m.scheduler.ScheduleAsync(func() error {
			if err != nil {
				reject(err.Error())
			} else {
				resolve(nil)
			}
			return nil
		})
	}()

	return m.vm.ToValue(promise)
}
