package manga_providers

import (
	"cmp"
	"fmt"
	"net/url"
	hibikemanga "seanime/internal/extension/hibike/manga"
	"seanime/internal/util"
	"seanime/internal/util/comparison"
	"slices"
	"strings"
	"time"

	"github.com/imroc/req/v3"
	"github.com/rs/zerolog"
)

type (
	ComicK struct {
		Url    string
		Client *req.Client
		logger *zerolog.Logger
	}

	ComicKResultItem struct {
		ID                   int     `json:"id"`
		HID                  string  `json:"hid"`
		Slug                 string  `json:"slug"`
		Title                string  `json:"title"`
		Country              string  `json:"country"`
		Rating               string  `json:"rating"`
		BayesianRating       string  `json:"bayesian_rating"`
		RatingCount          int     `json:"rating_count"`
		FollowCount          int     `json:"follow_count"`
		Description          string  `json:"desc"`
		Status               int     `json:"status"`
		LastChapter          float64 `json:"last_chapter"`
		TranslationCompleted bool    `json:"translation_completed"`
		ViewCount            int     `json:"view_count"`
		ContentRating        string  `json:"content_rating"`
		Demographic          int     `json:"demographic"`
		UploadedAt           string  `json:"uploaded_at"`
		Genres               []int   `json:"genres"`
		CreatedAt            string  `json:"created_at"`
		UserFollowCount      int     `json:"user_follow_count"`
		Year                 int     `json:"year"`
		MuComics             struct {
			Year int `json:"year"`
		} `json:"mu_comics"`
		MdTitles []struct {
			Title string `json:"title"`
		} `json:"md_titles"`
		MdCovers []struct {
			W     int    `json:"w"`
			H     int    `json:"h"`
			B2Key string `json:"b2key"`
		} `json:"md_covers"`
		Highlight string `json:"highlight"`
	}
)

func NewComicK(logger *zerolog.Logger) *ComicK {
	client := req.C().
		SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36").
		SetTimeout(60 * time.Second).
		EnableInsecureSkipVerify().
		ImpersonateSafari()

	return &ComicK{
		Url:    "https://api.comick.fun",
		Client: client,
		logger: logger,
	}
}

// DEVNOTE: Each chapter ID is a unique string provided by ComicK

func (c *ComicK) GetSettings() hibikemanga.Settings {
	return hibikemanga.Settings{
		SupportsMultiScanlator: false,
		SupportsMultiLanguage:  false,
	}
}

func (c *ComicK) Search(opts hibikemanga.SearchOptions) ([]*hibikemanga.SearchResult, error) {
	searchUrl := fmt.Sprintf("%s/v1.0/search?q=%s&limit=25&page=1", c.Url, url.QueryEscape(opts.Query))
	if opts.Year != 0 {
		searchUrl += fmt.Sprintf("&from=%d&to=%d", opts.Year, opts.Year)
	}

	c.logger.Debug().Str("searchUrl", searchUrl).Msg("comick: Searching manga")

	var data []*ComicKResultItem
	resp, err := c.Client.R().
		SetSuccessResult(&data).
		Get(searchUrl)

	if err != nil {
		c.logger.Error().Err(err).Msg("comick: Failed to send request")
		return nil, fmt.Errorf("failed to send request: %w", err)
	}

	if !resp.IsSuccessState() {
		c.logger.Error().Str("status", resp.Status).Msg("comick: Request failed")
		return nil, fmt.Errorf("failed to reach API: status %s", resp.Status)
	}

	results := make([]*hibikemanga.SearchResult, 0)
	for _, result := range data {

		// Skip fan-colored manga
		if strings.Contains(result.Slug, "fan-colored") {
			continue
		}

		var coverURL string
		if len(result.MdCovers) > 0 && result.MdCovers[0].B2Key != "" {
			coverURL = "https://meo.comick.pictures/" + result.MdCovers[0].B2Key
		}

		altTitles := make([]string, len(result.MdTitles))
		for j, title := range result.MdTitles {
			altTitles[j] = title.Title
		}

		// DEVNOTE: We don't compare to alt titles because ComicK's synonyms aren't good
		compRes, _ := comparison.FindBestMatchWithSorensenDice(&opts.Query, []*string{&result.Title})

		results = append(results, &hibikemanga.SearchResult{
			ID:           result.HID,
			Title:        cmp.Or(result.Title, result.Slug),
			Synonyms:     altTitles,
			Image:        coverURL,
			Year:         result.Year,
			SearchRating: compRes.Rating,
			Provider:     ComickProvider,
		})
	}

	if len(results) == 0 {
		c.logger.Warn().Msg("comick: No results found")
		return nil, ErrNoChapters
	}

	c.logger.Info().Int("count", len(results)).Msg("comick: Found results")

	return results, nil
}

func (c *ComicK) FindChapters(id string) ([]*hibikemanga.ChapterDetails, error) {
	ret := make([]*hibikemanga.ChapterDetails, 0)

	c.logger.Debug().Str("mangaId", id).Msg("comick: Fetching chapters")

	uri := fmt.Sprintf("%s/comic/%s/chapters?lang=en&page=0&limit=1000000&chap-order=1", c.Url, id)

	var data struct {
		Chapters []*ComicChapter `json:"chapters"`
	}

	resp, err := c.Client.R().
		SetSuccessResult(&data).
		Get(uri)

	if err != nil {
		c.logger.Error().Err(err).Msg("comick: Failed to send request")
		return nil, fmt.Errorf("failed to send request: %w", err)
	}

	if !resp.IsSuccessState() {
		c.logger.Error().Str("status", resp.Status).Msg("comick: Request failed")
		return nil, fmt.Errorf("failed to decode response: status %s", resp.Status)
	}

	chapters := make([]*hibikemanga.ChapterDetails, 0)
	chaptersMap := make(map[string]*hibikemanga.ChapterDetails)
	count := 0
	for _, chapter := range data.Chapters {
		if chapter.Chap == "" || chapter.Lang != "en" {
			continue
		}
		title := "Chapter " + chapter.Chap + " "

		if title == "" {
			if chapter.Title == "" {
				title = "Oneshot"
			} else {
				title = chapter.Title
			}
		}
		title = strings.TrimSpace(title)

		prev, ok := chaptersMap[chapter.Chap]
		rating := chapter.UpCount - chapter.DownCount

		if !ok || rating > prev.Rating {
			if !ok {
				count++
			}
			chaptersMap[chapter.Chap] = &hibikemanga.ChapterDetails{
				Provider:  ComickProvider,
				ID:        chapter.HID,
				Title:     title,
				Index:     uint(count),
				URL:       fmt.Sprintf("%s/chapter/%s", c.Url, chapter.HID),
				Chapter:   chapter.Chap,
				Rating:    rating,
				UpdatedAt: chapter.UpdatedAt,
			}
		}
	}

	for _, chapter := range chaptersMap {
		chapters = append(chapters, chapter)
	}

	// Sort chapters by index
	slices.SortStableFunc(chapters, func(i, j *hibikemanga.ChapterDetails) int {
		return cmp.Compare(i.Index, j.Index)
	})

	ret = append(ret, chapters...)

	if len(ret) == 0 {
		c.logger.Warn().Msg("comick: No chapters found")
		return nil, ErrNoChapters
	}

	c.logger.Info().Int("count", len(ret)).Msg("comick: Found chapters")

	return ret, nil
}

func (c *ComicK) FindChapterPages(id string) ([]*hibikemanga.ChapterPage, error) {
	ret := make([]*hibikemanga.ChapterPage, 0)

	c.logger.Debug().Str("chapterId", id).Msg("comick: Finding chapter pages")

	uri := fmt.Sprintf("%s/chapter/%s", c.Url, id)

	var data struct {
		Chapter *ComicChapter `json:"chapter"`
	}

	resp, err := c.Client.R().
		SetHeader("User-Agent", util.GetRandomUserAgent()).
		SetSuccessResult(&data).
		Get(uri)

	if err != nil {
		c.logger.Error().Err(err).Msg("comick: Failed to send request")
		return nil, fmt.Errorf("failed to send request: %w", err)
	}

	if !resp.IsSuccessState() {
		c.logger.Error().Str("status", resp.Status).Msg("comick: Request failed")
		return nil, fmt.Errorf("failed to decode response: status %s", resp.Status)
	}

	if data.Chapter == nil {
		c.logger.Error().Msg("comick: Chapter not found")
		return nil, fmt.Errorf("chapter not found")
	}

	for index, image := range data.Chapter.MdImages {
		ret = append(ret, &hibikemanga.ChapterPage{
			Provider: ComickProvider,
			URL:      fmt.Sprintf("https://meo.comick.pictures/%s", image.B2Key),
			Index:    index,
			Headers:  make(map[string]string),
		})
	}

	if len(ret) == 0 {
		c.logger.Warn().Msg("comick: No pages found")
		return nil, ErrNoPages
	}

	c.logger.Info().Int("count", len(ret)).Msg("comick: Found pages")

	return ret, nil

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

type Comic struct {
	ID      int    `json:"id"`
	HID     string `json:"hid"`
	Title   string `json:"title"`
	Country string `json:"country"`
	Status  int    `json:"status"`
	Links   struct {
		AL  string `json:"al"`
		AP  string `json:"ap"`
		BW  string `json:"bw"`
		KT  string `json:"kt"`
		MU  string `json:"mu"`
		AMZ string `json:"amz"`
		CDJ string `json:"cdj"`
		EBJ string `json:"ebj"`
		MAL string `json:"mal"`
		RAW string `json:"raw"`
	} `json:"links"`
	LastChapter          interface{}   `json:"last_chapter"`
	ChapterCount         int           `json:"chapter_count"`
	Demographic          int           `json:"demographic"`
	Hentai               bool          `json:"hentai"`
	UserFollowCount      int           `json:"user_follow_count"`
	FollowRank           int           `json:"follow_rank"`
	CommentCount         int           `json:"comment_count"`
	FollowCount          int           `json:"follow_count"`
	Description          string        `json:"desc"`
	Parsed               string        `json:"parsed"`
	Slug                 string        `json:"slug"`
	Mismatch             interface{}   `json:"mismatch"`
	Year                 int           `json:"year"`
	BayesianRating       interface{}   `json:"bayesian_rating"`
	RatingCount          int           `json:"rating_count"`
	ContentRating        string        `json:"content_rating"`
	TranslationCompleted bool          `json:"translation_completed"`
	RelateFrom           []interface{} `json:"relate_from"`
	Mies                 interface{}   `json:"mies"`
	MdTitles             []struct {
		Title string `json:"title"`
	} `json:"md_titles"`
	MdComicMdGenres []struct {
		MdGenres struct {
			Name  string      `json:"name"`
			Type  interface{} `json:"type"`
			Slug  string      `json:"slug"`
			Group string      `json:"group"`
		} `json:"md_genres"`
	} `json:"md_comic_md_genres"`
	MuComics struct {
		LicensedInEnglish interface{} `json:"licensed_in_english"`
		MuComicCategories []struct {
			MuCategories struct {
				Title string `json:"title"`
				Slug  string `json:"slug"`
			} `json:"mu_categories"`
			PositiveVote int `json:"positive_vote"`
			NegativeVote int `json:"negative_vote"`
		} `json:"mu_comic_categories"`
	} `json:"mu_comics"`
	MdCovers []struct {
		Vol   interface{} `json:"vol"`
		W     int         `json:"w"`
		H     int         `json:"h"`
		B2Key string      `json:"b2key"`
	} `json:"md_covers"`
	Iso6391    string `json:"iso639_1"`
	LangName   string `json:"lang_name"`
	LangNative string `json:"lang_native"`
}

type ComicChapter struct {
	ID        int      `json:"id"`
	Chap      string   `json:"chap"`
	Title     string   `json:"title"`
	Vol       string   `json:"vol,omitempty"`
	Lang      string   `json:"lang"`
	CreatedAt string   `json:"created_at"`
	UpdatedAt string   `json:"updated_at"`
	UpCount   int      `json:"up_count"`
	DownCount int      `json:"down_count"`
	GroupName []string `json:"group_name"`
	HID       string   `json:"hid"`
	MdImages  []struct {
		Name  string `json:"name"`
		W     int    `json:"w"`
		H     int    `json:"h"`
		S     int    `json:"s"`
		B2Key string `json:"b2key"`
	} `json:"md_images"`
}
