package core

import (
	"errors"
	"fmt"
	"html/template"
	"sort"
	"strings"
	"sync"
	"time"

	http "codeberg.org/vnpower/pixivfe/v2/core/http"
	session "codeberg.org/vnpower/pixivfe/v2/core/session"
	"github.com/goccy/go-json"
	"github.com/gofiber/fiber/v2"
)

// Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
// Those values are saved in `xRestrict`
// 0: Safe
// 1: R18
// 2: R18G
type xRestrict int

const (
	Safe xRestrict = 0
	R18  xRestrict = 1
	R18G xRestrict = 2
)

var xRestrictModel = map[xRestrict]string{
	Safe: "",
	R18:  "R18",
	R18G: "R18G",
}

// Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
// Those values are saved in `aiType`
// 0: Not rated / Unknown
// 1: Not AI-generated
// 2: AI-generated

type aiType int

const (
	Unrated aiType = 0
	NotAI   aiType = 1
	AI      aiType = 2
)

var aiTypeModel = map[aiType]string{
	Unrated: "Unrated",
	NotAI:   "Not AI",
	AI:      "AI",
}

type ImageResponse struct {
	Width  int               `json:"width"`
	Height int               `json:"height"`
	Urls   map[string]string `json:"urls"`
}

type Image struct {
	Width    int
	Height   int
	Small    string
	Medium   string
	Large    string
	Original string
}

type Tag struct {
	Name           string `json:"tag"`
	TranslatedName string `json:"translation"`
}

type Comment struct {
	AuthorID   string `json:"userId"`
	AuthorName string `json:"userName"`
	Avatar     string `json:"img"`
	Context    string `json:"comment"`
	Stamp      string `json:"stampId"`
	Date       string `json:"commentDate"`
}

type UserBrief struct {
	ID     string `json:"userId"`
	Name   string `json:"name"`
	Avatar string `json:"imageBig"`
}

type ArtworkBrief struct {
	ID           string `json:"id"`
	Title        string `json:"title"`
	ArtistID     string `json:"userId"`
	ArtistName   string `json:"userName"`
	ArtistAvatar string `json:"profileImageUrl"`
	Thumbnail    string `json:"url"`
	Pages        int    `json:"pageCount"`
	XRestrict    int    `json:"xRestrict"`
	AiType       int    `json:"aiType"`
	Bookmarked   any    `json:"bookmarkData"`
	IllustType   int    `json:"illustType"`
}

type Illust struct {
	ID              string        `json:"id"`
	Title           string        `json:"title"`
	Description     template.HTML `json:"description"`
	UserID          string        `json:"userId"`
	UserName        string        `json:"userName"`
	UserAccount     string        `json:"userAccount"`
	Date            time.Time     `json:"uploadDate"`
	Images          []Image
	Tags            []Tag     `json:"tags"`
	Pages           int       `json:"pageCount"`
	Bookmarks       int       `json:"bookmarkCount"`
	Likes           int       `json:"likeCount"`
	Comments        int       `json:"commentCount"`
	Views           int       `json:"viewCount"`
	CommentDisabled int       `json:"commentOff"`
	SanityLevel     int       `json:"sl"`
	XRestrict       xRestrict `json:"xRestrict"`
	AiType          aiType    `json:"aiType"`
	BookmarkData    any       `json:"bookmarkData"`
	Liked           bool      `json:"likeData"`
	User            UserBrief
	RecentWorks     []ArtworkBrief
	RelatedWorks    []ArtworkBrief
	CommentsList    []Comment
	IsUgoira        bool
	BookmarkID      string
}

func GetUserBasicInformation(c *fiber.Ctx, id string) (UserBrief, error) {
	var user UserBrief

	URL := http.GetUserInformationURL(id)

	response, err := http.UnwrapWebAPIRequest(c.Context(), URL, "")
	if err != nil {
		return user, err
	}
	response = session.ProxyImageUrl(c, response)

	err = json.Unmarshal([]byte(response), &user)
	if err != nil {
		return user, err
	}

	return user, nil
}

func GetArtworkImages(c *fiber.Ctx, id string) ([]Image, error) {
	var resp []ImageResponse
	var images []Image

	URL := http.GetArtworkImagesURL(id)

	response, err := http.UnwrapWebAPIRequest(c.Context(), URL, "")
	if err != nil {
		return nil, err
	}
	response = session.ProxyImageUrl(c, response)

	err = json.Unmarshal([]byte(response), &resp)
	if err != nil {
		return images, err
	}

	// Extract and proxy every images
	for _, imageRaw := range resp {
		var image Image

		// this is the original art dimention, not the "regular" art dimension
		// the image ratio of "regular" is close to Width/Height
		// maybe not useful
		image.Width = imageRaw.Width
		image.Height = imageRaw.Height

		image.Small = imageRaw.Urls["thumb_mini"]
		image.Medium = imageRaw.Urls["small"]
		image.Large = imageRaw.Urls["regular"]
		image.Original = imageRaw.Urls["original"]

		images = append(images, image)
	}

	return images, nil
}

func GetArtworkComments(c *fiber.Ctx, id string) ([]Comment, error) {
	var body struct {
		Comments []Comment `json:"comments"`
	}

	URL := http.GetArtworkCommentsURL(id)

	response, err := http.UnwrapWebAPIRequest(c.Context(), URL, "")
	if err != nil {
		return nil, err
	}
	response = session.ProxyImageUrl(c, response)

	err = json.Unmarshal([]byte(response), &body)
	if err != nil {
		return nil, err
	}

	return body.Comments, nil
}

func GetRelatedArtworks(c *fiber.Ctx, id string) ([]ArtworkBrief, error) {
	var body struct {
		Illusts []ArtworkBrief `json:"illusts"`
	}

	// TODO: keep the hard-coded limit?
	URL := http.GetArtworkRelatedURL(id, 96)

	response, err := http.UnwrapWebAPIRequest(c.Context(), URL, "")
	if err != nil {
		return nil, err
	}

	response = session.ProxyImageUrl(c, response)

	err = json.Unmarshal([]byte(response), &body)
	if err != nil {
		return nil, err
	}

	return body.Illusts, nil
}

func GetArtworkByID(c *fiber.Ctx, id string, full bool) (*Illust, error) {
	URL := http.GetArtworkInformationURL(id)

	token := session.GetPixivToken(c)
	response, err := http.UnwrapWebAPIRequest(c.Context(), URL, token)
	if err != nil {
		return nil, err
	}

	var illust struct {
		*Illust

		// recent illustrations by same user
		Recent  map[int]any     `json:"userIllusts"`
		RawTags json.RawMessage `json:"tags"`
	}

	// Parse basic illust information
	err = json.Unmarshal([]byte(response), &illust)
	if err != nil {
		return nil, err
	}

	if illust.BookmarkData != nil {
		t := illust.BookmarkData.(map[string]any)
		illust.BookmarkID = t["id"].(string)
	}

	// Begin testing here

	wg := sync.WaitGroup{}
	cerr := make(chan error, 6)

	wg.Add(3)

	go func() {
		// Get illust images
		defer wg.Done()
		images, err := GetArtworkImages(c, id)
		if err != nil {

			cerr <- err
			return
		}
		illust.Images = images
	}()

	go func() {
		// Get basic user information (the URL above does not contain avatars)
		defer wg.Done()
		var err error
		userInfo, err := GetUserBasicInformation(c, illust.UserID)
		if err != nil {
			cerr <- err
			return
		}
		illust.User = userInfo
	}()

	go func() {
		defer wg.Done()
		var err error
		// Extract tags
		var tags struct {
			Tags []struct {
				Tag         string            `json:"tag"`
				Translation map[string]string `json:"translation"`
			} `json:"tags"`
		}
		err = json.Unmarshal(illust.RawTags, &tags)
		if err != nil {
			cerr <- err
			return
		}

		var tagsList []Tag
		for _, tag := range tags.Tags {
			var newTag Tag
			newTag.Name = tag.Tag
			newTag.TranslatedName = tag.Translation["en"]

			tagsList = append(tagsList, newTag)
		}
		illust.Tags = tagsList
	}()

	if full {
		wg.Add(3)

		go func() {
			defer wg.Done()
			var err error
			// Get recent artworks
			ids := make([]int, 0)

			for k := range illust.Recent {
				ids = append(ids, k)
			}

			sort.Sort(sort.Reverse(sort.IntSlice(ids)))

			idsString := ""
			count := min(len(ids), 20)

			for i := 0; i < count; i++ {
				idsString += fmt.Sprintf("&ids[]=%d", ids[i])
			}

			recent, err := GetUserArtworks(c, illust.UserID, idsString)
			if err != nil {
				cerr <- err
				return
			}
			sort.Slice(recent[:], func(i, j int) bool {
				left := recent[i].ID
				right := recent[j].ID
				return numberGreaterThan(left, right)
			})
			illust.RecentWorks = recent
		}()

		go func() {
			defer wg.Done()
			var err error
			related, err := GetRelatedArtworks(c, id)
			if err != nil {
				cerr <- err
				return
			}
			illust.RelatedWorks = related
		}()

		go func() {
			defer wg.Done()
			if illust.CommentDisabled == 1 {
				return
			}
			var err error
			comments, err := GetArtworkComments(c, id)
			if err != nil {
				cerr <- err
				return
			}
			illust.CommentsList = comments
		}()
	}

	wg.Wait()
	close(cerr)

	all_errors := []error{}
	for suberr := range cerr {
		all_errors = append(all_errors, suberr)
	}
	err_summary := errors.Join(all_errors...)
	if err_summary != nil {
		return nil, err_summary
	}

	// If this artwork is an ugoira
	illust.IsUgoira = strings.Contains(illust.Images[0].Original, "ugoira")

	return illust.Illust, nil
}
