package arn

import (
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/aerogo/nano"
	"github.com/animenotifier/notify.moe/arn/validate"
	"github.com/animenotifier/twist"

	"github.com/akyoto/color"
	"github.com/animenotifier/kitsu"
	"github.com/animenotifier/shoboi"
)

// AnimeDateFormat describes the anime date format for the date conversion.
const AnimeDateFormat = validate.DateFormat

// AnimeSourceHumanReadable maps the anime source to a human readable version.
var AnimeSourceHumanReadable = map[string]string{}

// Register a list of supported anime status and source types.
func init() {
	DataLists["anime-types"] = []*Option{
		{"tv", "TV"},
		{"movie", "Movie"},
		{"ova", "OVA"},
		{"ona", "ONA"},
		{"special", "Special"},
		{"music", "Music"},
	}

	DataLists["anime-status"] = []*Option{
		{"current", "Current"},
		{"finished", "Finished"},
		{"upcoming", "Upcoming"},
		{"tba", "To be announced"},
	}

	DataLists["anime-sources"] = []*Option{
		{"", "Unknown"},
		{"original", "Original"},
		{"manga", "Manga"},
		{"novel", "Novel"},
		{"light novel", "Light novel"},
		{"visual novel", "Visual novel"},
		{"game", "Game"},
		{"book", "Book"},
		{"4-koma manga", "4-koma Manga"},
		{"music", "Music"},
		{"picture book", "Picture book"},
		{"web manga", "Web manga"},
		{"other", "Other"},
	}

	for _, option := range DataLists["anime-sources"] {
		AnimeSourceHumanReadable[option.Value] = option.Label
	}
}

// AnimeID represents an anime ID.
type AnimeID = string

// Anime represents an anime.
type Anime struct {
	ID            AnimeID          `json:"id" primary:"true"`
	Type          string           `json:"type" editable:"true" datalist:"anime-types"`
	Title         *MediaTitle      `json:"title" editable:"true"`
	Summary       string           `json:"summary" editable:"true" type:"textarea"`
	Status        string           `json:"status" editable:"true" datalist:"anime-status"`
	Genres        []string         `json:"genres" editable:"true"`
	StartDate     string           `json:"startDate" editable:"true"`
	EndDate       string           `json:"endDate" editable:"true"`
	EpisodeCount  int              `json:"episodeCount" editable:"true"`
	EpisodeLength int              `json:"episodeLength" editable:"true"`
	Source        string           `json:"source" editable:"true" datalist:"anime-sources"`
	Image         Image            `json:"image"`
	FirstChannel  string           `json:"firstChannel"`
	Rating        *AnimeRating     `json:"rating"`
	Popularity    *AnimePopularity `json:"popularity"`
	Trailers      []*ExternalMedia `json:"trailers" editable:"true"`
	EpisodeIDs    []string         `json:"episodes" editable:"true"`

	// Mixins
	hasMappings
	hasPosts
	hasLikes
	hasCreator
	hasEditor
	hasDraft

	// Company IDs
	StudioIDs   []string `json:"studios" editable:"true"`
	ProducerIDs []string `json:"producers" editable:"true"`
	LicensorIDs []string `json:"licensors" editable:"true"`

	// Links to external websites
	Links []*Link `json:"links" editable:"true"`

	// SynopsisSource string        `json:"synopsisSource" editable:"true"`
	// Hashtag        string        `json:"hashtag"`
}

// NewAnime creates a new anime.
func NewAnime() *Anime {
	return &Anime{
		ID:         GenerateID("Anime"),
		Title:      &MediaTitle{},
		Rating:     &AnimeRating{},
		Popularity: &AnimePopularity{},
		Trailers:   []*ExternalMedia{},
		hasCreator: hasCreator{
			Created: DateTimeUTC(),
		},
		hasMappings: hasMappings{
			Mappings: []*Mapping{},
		},
	}
}

// GetAnime gets the anime with the given ID.
func GetAnime(id AnimeID) (*Anime, error) {
	obj, err := DB.Get("Anime", id)

	if err != nil {
		return nil, err
	}

	return obj.(*Anime), nil
}

// TitleByUser returns the preferred title for the given user.
func (anime *Anime) TitleByUser(user *User) string {
	return anime.Title.ByUser(user)
}

// AddStudio adds the company ID to the studio ID list if it doesn't exist already.
func (anime *Anime) AddStudio(companyID string) {
	// Is the ID valid?
	if companyID == "" {
		return
	}

	// If it already exists we don't need to add it
	for _, id := range anime.StudioIDs {
		if id == companyID {
			return
		}
	}

	anime.StudioIDs = append(anime.StudioIDs, companyID)
}

// AddProducer adds the company ID to the producer ID list if it doesn't exist already.
func (anime *Anime) AddProducer(companyID string) {
	// Is the ID valid?
	if companyID == "" {
		return
	}

	// If it already exists we don't need to add it
	for _, id := range anime.ProducerIDs {
		if id == companyID {
			return
		}
	}

	anime.ProducerIDs = append(anime.ProducerIDs, companyID)
}

// AddLicensor adds the company ID to the licensor ID list if it doesn't exist already.
func (anime *Anime) AddLicensor(companyID string) {
	// Is the ID valid?
	if companyID == "" {
		return
	}

	// If it already exists we don't need to add it
	for _, id := range anime.LicensorIDs {
		if id == companyID {
			return
		}
	}

	anime.LicensorIDs = append(anime.LicensorIDs, companyID)
}

// Studios returns the list of studios for this anime.
func (anime *Anime) Studios() []*Company {
	companies := []*Company{}

	for _, obj := range DB.GetMany("Company", anime.StudioIDs) {
		if obj == nil {
			continue
		}

		companies = append(companies, obj.(*Company))
	}

	return companies
}

// Producers returns the list of producers for this anime.
func (anime *Anime) Producers() []*Company {
	companies := []*Company{}

	for _, obj := range DB.GetMany("Company", anime.ProducerIDs) {
		if obj == nil {
			continue
		}

		companies = append(companies, obj.(*Company))
	}

	return companies
}

// Licensors returns the list of licensors for this anime.
func (anime *Anime) Licensors() []*Company {
	companies := []*Company{}

	for _, obj := range DB.GetMany("Company", anime.LicensorIDs) {
		if obj == nil {
			continue
		}

		companies = append(companies, obj.(*Company))
	}

	return companies
}

// Prequels returns the list of prequels for that anime.
func (anime *Anime) Prequels() []*Anime {
	prequels := []*Anime{}
	relations := anime.Relations()

	relations.Lock()
	defer relations.Unlock()

	for _, relation := range relations.Items {
		if relation.Type != "prequel" {
			continue
		}

		prequel := relation.Anime()

		if prequel == nil {
			color.Red("Anime %s has invalid anime relation ID %s", anime.ID, relation.AnimeID)
			continue
		}

		prequels = append(prequels, prequel)
	}

	return prequels
}

// ImageLink ...
func (anime *Anime) ImageLink(size string) string {
	extension := ".jpg"

	if size == "original" {
		extension = anime.Image.Extension
	}

	return fmt.Sprintf("//%s/images/anime/%s/%s%s?%v", MediaHost, size, anime.ID, extension, anime.Image.LastModified)
}

// HasImage returns whether the anime has an image or not.
func (anime *Anime) HasImage() bool {
	return anime.Image.Extension != "" && anime.Image.Width > 0
}

// AverageColor returns the average color of the image.
func (anime *Anime) AverageColor() string {
	color := anime.Image.AverageColor

	if color.Hue == 0 && color.Saturation == 0 && color.Lightness == 0 {
		return ""
	}

	return color.String()
}

// Season returns the season the anime started airing in.
func (anime *Anime) Season() string {
	if !validate.Date(anime.StartDate) && !validate.YearMonth(anime.StartDate) {
		return ""
	}

	return DateToSeason(anime.StartDateTime())
}

// Characters ...
func (anime *Anime) Characters() *AnimeCharacters {
	characters, _ := GetAnimeCharacters(anime.ID)

	if characters != nil {
		// TODO: Sort by role in sync-characters job
		// Sort by role
		sort.Slice(characters.Items, func(i, j int) bool {
			// A little trick because "main" < "supporting"
			return characters.Items[i].Role < characters.Items[j].Role
		})
	}

	return characters
}

// Relations ...
func (anime *Anime) Relations() *AnimeRelations {
	relations, _ := GetAnimeRelations(anime.ID)
	return relations
}

// Link returns the URI to the anime page.
func (anime *Anime) Link() string {
	return "/anime/" + anime.ID
}

// StartDateTime returns the start date as a time object.
func (anime *Anime) StartDateTime() time.Time {
	format := AnimeDateFormat

	switch {
	case len(anime.StartDate) >= len(AnimeDateFormat):
		// ...
	case len(anime.StartDate) >= len("2006-01"):
		format = "2006-01"
	case len(anime.StartDate) >= len("2006"):
		format = "2006"
	}

	t, _ := time.Parse(format, anime.StartDate)
	return t
}

// EndDateTime returns the end date as a time object.
func (anime *Anime) EndDateTime() time.Time {
	format := AnimeDateFormat

	switch {
	case len(anime.EndDate) >= len(AnimeDateFormat):
		// ...
	case len(anime.EndDate) >= len("2006-01"):
		format = "2006-01"
	case len(anime.EndDate) >= len("2006"):
		format = "2006"
	}

	t, _ := time.Parse(format, anime.EndDate)
	return t
}

// Episodes returns the anime episodes.
func (anime *Anime) Episodes() EpisodeList {
	objects := DB.GetMany("Episode", anime.EpisodeIDs)
	episodes := make([]*Episode, 0, len(anime.EpisodeIDs))

	for _, obj := range objects {
		if obj == nil {
			continue
		}

		episodes = append(episodes, obj.(*Episode))
	}

	return episodes
}

// UsersWatchingOrPlanned returns a list of users who are watching the anime right now.
func (anime *Anime) UsersWatchingOrPlanned() []*User {
	users := FilterUsers(func(user *User) bool {
		item := user.AnimeList().Find(anime.ID)

		if item == nil {
			return false
		}

		return item.Status == AnimeListStatusWatching || item.Status == AnimeListStatusPlanned
	})

	return users
}

// RefreshEpisodes will refresh the episode data.
func (anime *Anime) RefreshEpisodes() error {
	// Fetch episodes
	episodes := anime.Episodes()

	// Save number of available episodes for comparison later
	oldAvailableCount := episodes.AvailableCount()

	// Shoboi
	shoboiEpisodes, err := anime.ShoboiEpisodes()

	if err != nil {
		return err
	}

	episodes = episodes.Merge(shoboiEpisodes)

	// AnimeTwist
	twistEpisodes, err := anime.TwistEpisodes()

	if err != nil {
		return err
	}

	episodes = episodes.Merge(twistEpisodes)

	// Count number of available episodes
	newAvailableCount := episodes.AvailableCount()

	if anime.Status != "finished" && newAvailableCount > oldAvailableCount {
		// New episodes have been released.
		// Notify all users who are watching the anime.
		go func() {
			for _, user := range anime.UsersWatchingOrPlanned() {
				if !user.Settings().Notification.AnimeEpisodeReleases {
					continue
				}

				user.SendNotification(&PushNotification{
					Title:   anime.Title.ByUser(user),
					Message: "Episode " + strconv.Itoa(newAvailableCount) + " has been released!",
					Icon:    anime.ImageLink("medium"),
					Link:    "https://notify.moe" + anime.Link(),
					Type:    NotificationTypeAnimeEpisode,
				})
			}
		}()
	}

	// Number remaining episodes
	startNumber := 0

	for _, episode := range episodes {
		if episode.Number != -1 {
			startNumber = episode.Number
			continue
		}

		startNumber++
		episode.Number = startNumber
	}

	// Guess airing dates
	oneWeek := 7 * 24 * time.Hour
	lastAiringDate := ""
	timeDifference := oneWeek

	for _, episode := range episodes {
		if validate.DateTime(episode.AiringDate.Start) {
			if lastAiringDate != "" {
				a, _ := time.Parse(time.RFC3339, lastAiringDate)
				b, _ := time.Parse(time.RFC3339, episode.AiringDate.Start)
				timeDifference = b.Sub(a)

				// Cap time difference at one week
				if timeDifference > oneWeek {
					timeDifference = oneWeek
				}
			}

			lastAiringDate = episode.AiringDate.Start
			continue
		}

		// Add 1 week to the last known airing date
		nextAiringDate, _ := time.Parse(time.RFC3339, lastAiringDate)
		nextAiringDate = nextAiringDate.Add(timeDifference)

		// Guess start and end time
		episode.AiringDate.Start = nextAiringDate.Format(time.RFC3339)
		episode.AiringDate.End = nextAiringDate.Add(30 * time.Minute).Format(time.RFC3339)

		// Set this date as the new last known airing date
		lastAiringDate = episode.AiringDate.Start
	}

	// Save new episode ID list
	episodeIDs := make([]string, len(episodes))

	for index, episode := range episodes {
		episodeIDs[index] = episode.ID
		episode.AnimeID = anime.ID
		episode.Save()
	}

	anime.EpisodeIDs = episodeIDs
	anime.Save()
	return nil
}

// ShoboiEpisodes returns a slice of episode info from cal.syoboi.jp.
func (anime *Anime) ShoboiEpisodes() (EpisodeList, error) {
	shoboiID := anime.GetMapping("shoboi/anime")

	if shoboiID == "" {
		return nil, errors.New("Missing shoboi/anime mapping")
	}

	shoboiAnime, err := shoboi.GetAnime(shoboiID)

	if err != nil {
		return nil, err
	}

	arnEpisodes := []*Episode{}
	shoboiEpisodes := shoboiAnime.Episodes()

	for _, shoboiEpisode := range shoboiEpisodes {
		episode := NewAnimeEpisode()
		episode.Number = shoboiEpisode.Number
		episode.Title.Japanese = shoboiEpisode.TitleJapanese

		// Try to get airing date
		airingDate := shoboiEpisode.AiringDate

		if airingDate != nil {
			episode.AiringDate.Start = airingDate.Start
			episode.AiringDate.End = airingDate.End
		} else {
			episode.AiringDate.Start = ""
			episode.AiringDate.End = ""
		}

		arnEpisodes = append(arnEpisodes, episode)
	}

	return arnEpisodes, nil
}

// TwistEpisodes returns a slice of episode info from twist.moe.
func (anime *Anime) TwistEpisodes() (EpisodeList, error) {
	idList, err := GetIDList("animetwist index")

	if err != nil {
		return nil, err
	}

	// Does the index contain the ID?
	kitsuID := anime.GetMapping("kitsu/anime")
	found := false

	for _, id := range idList {
		if id == kitsuID {
			found = true
			break
		}
	}

	// If the ID is not the index we don't need to query the feed
	if !found {
		return nil, errors.New("Not available in twist.moe anime index")
	}

	// Get twist.moe feed
	feed, err := twist.GetFeedByKitsuID(kitsuID)

	if err != nil {
		return nil, err
	}

	episodes := feed.Episodes

	// Sort by episode number
	sort.Slice(episodes, func(a, b int) bool {
		return episodes[a].Number < episodes[b].Number
	})

	arnEpisodes := []*Episode{}

	for _, episode := range episodes {
		arnEpisode := NewAnimeEpisode()
		arnEpisode.Number = episode.Number
		arnEpisode.Links = map[string]string{
			"twist.moe": strings.Replace(episode.Link, "https://test.twist.moe/", "https://twist.moe/", 1),
		}

		arnEpisodes = append(arnEpisodes, arnEpisode)
	}

	return arnEpisodes, nil
}

// UpcomingEpisodes ...
func (anime *Anime) UpcomingEpisodes() []*UpcomingEpisode {
	var upcomingEpisodes []*UpcomingEpisode
	now := time.Now().UTC().Format(time.RFC3339)

	for _, episode := range anime.Episodes() {
		if episode.AiringDate.Start > now && validate.DateTime(episode.AiringDate.Start) {
			upcomingEpisodes = append(upcomingEpisodes, &UpcomingEpisode{
				Anime:   anime,
				Episode: episode,
			})
		}
	}

	return upcomingEpisodes
}

// UpcomingEpisode ...
func (anime *Anime) UpcomingEpisode() *UpcomingEpisode {
	now := time.Now().UTC().Format(time.RFC3339)

	for _, episode := range anime.Episodes() {
		if episode.AiringDate.Start > now && validate.DateTime(episode.AiringDate.Start) {
			return &UpcomingEpisode{
				Anime:   anime,
				Episode: episode,
			}
		}
	}

	return nil
}

// EpisodeCountString ...
func (anime *Anime) EpisodeCountString() string {
	if anime.EpisodeCount == 0 {
		return "?"
	}

	return strconv.Itoa(anime.EpisodeCount)
}

// ImportKitsuMapping imports the given Kitsu mapping.
func (anime *Anime) ImportKitsuMapping(mapping *kitsu.Mapping) {
	switch mapping.Attributes.ExternalSite {
	case "myanimelist/anime":
		anime.SetMapping("myanimelist/anime", mapping.Attributes.ExternalID)
	case "anidb":
		anime.SetMapping("anidb/anime", mapping.Attributes.ExternalID)
	case "trakt":
		anime.SetMapping("trakt/anime", mapping.Attributes.ExternalID)
	// case "hulu":
	// 	anime.SetMapping("hulu/anime", mapping.Attributes.ExternalID)
	case "anilist":
		externalID := mapping.Attributes.ExternalID
		externalID = strings.TrimPrefix(externalID, "anime/")

		anime.SetMapping("anilist/anime", externalID)
	case "thetvdb", "thetvdb/series":
		externalID := mapping.Attributes.ExternalID
		slashPos := strings.Index(externalID, "/")

		if slashPos != -1 {
			externalID = externalID[:slashPos]
		}

		anime.SetMapping("thetvdb/anime", externalID)
	case "thetvdb/season":
		// Ignore
	default:
		color.Yellow("Unknown mapping: %s %s", mapping.Attributes.ExternalSite, mapping.Attributes.ExternalID)
	}
}

// TypeHumanReadable ...
func (anime *Anime) TypeHumanReadable() string {
	switch anime.Type {
	case "tv":
		return "TV"
	case "movie":
		return "Movie"
	case "ova":
		return "OVA"
	case "ona":
		return "ONA"
	case "special":
		return "Special"
	case "music":
		return "Music"
	default:
		return anime.Type
	}
}

// StatusHumanReadable ...
func (anime *Anime) StatusHumanReadable() string {
	switch anime.Status {
	case "finished":
		return "Finished"
	case "current":
		return "Airing"
	case "upcoming":
		return "Upcoming"
	case "tba":
		return "To be announced"
	default:
		return anime.Status
	}
}

// CalculatedStatus returns the status of the anime inferred by the start and end date.
func (anime *Anime) CalculatedStatus() string {
	// If we are past the end date, the anime is finished.
	if validate.Date(anime.EndDate) {
		end := anime.EndDateTime()

		if time.Since(end) > 0 {
			return "finished"
		}
	}

	// If we have a start date and we didn't reach the end date, it's either current or upcoming.
	if validate.Date(anime.StartDate) {
		start := anime.StartDateTime()

		if time.Since(start) > 0 {
			return "current"
		}

		return "upcoming"
	}

	// If we have no date information it's to be announced.
	return "tba"
}

// EpisodeByNumber returns the episode with the given number.
func (anime *Anime) EpisodeByNumber(number int) *Episode {
	for _, episode := range anime.Episodes() {
		if number == episode.Number {
			return episode
		}
	}

	return nil
}

// RefreshAnimeCharacters ...
func (anime *Anime) RefreshAnimeCharacters() (*AnimeCharacters, error) {
	resp, err := kitsu.GetAnimeCharactersForAnime(anime.GetMapping("kitsu/anime"))

	if err != nil {
		return nil, err
	}

	animeCharacters := &AnimeCharacters{
		AnimeID: anime.ID,
		Items:   []*AnimeCharacter{},
	}

	for _, incl := range resp.Included {
		if incl.Type != "animeCharacters" {
			continue
		}

		role := incl.Attributes["role"].(string)
		characterID := incl.Relationships.Character.Data.ID

		animeCharacter := &AnimeCharacter{
			CharacterID: characterID,
			Role:        role,
		}

		animeCharacters.Items = append(animeCharacters.Items, animeCharacter)
	}

	animeCharacters.Save()

	return animeCharacters, nil
}

// String implements the default string serialization.
func (anime *Anime) String() string {
	return anime.Title.Canonical
}

// GetID returns the ID.
func (anime *Anime) GetID() string {
	return anime.ID
}

// TypeName returns the type name.
func (anime *Anime) TypeName() string {
	return "Anime"
}

// Self returns the object itself.
func (anime *Anime) Self() Loggable {
	return anime
}

// StreamAnime returns a stream of all anime.
func StreamAnime() <-chan *Anime {
	channel := make(chan *Anime, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("Anime") {
			channel <- obj.(*Anime)
		}

		close(channel)
	}()

	return channel
}

// AllAnime returns a slice of all anime.
func AllAnime() []*Anime {
	all := make([]*Anime, 0, DB.Collection("Anime").Count())

	stream := StreamAnime()

	for obj := range stream {
		all = append(all, obj)
	}

	return all
}

// FilterAnime filters all anime by a custom function.
func FilterAnime(filter func(*Anime) bool) []*Anime {
	var filtered []*Anime

	channel := DB.All("Anime")

	for obj := range channel {
		realObject := obj.(*Anime)

		if filter(realObject) {
			filtered = append(filtered, realObject)
		}
	}

	return filtered
}

// // SetID performs a database-wide ID change.
// // Calling this will automatically save the anime.
// func (anime *Anime) SetID(newID string) {
// 	oldID := anime.ID

// 	// Update anime ID in character list
// 	characters, _ := GetAnimeCharacters(oldID)
// 	characters.Delete()
// 	characters.AnimeID = newID
// 	characters.Save()

// 	// Update anime ID in relation list
// 	relations, _ := GetAnimeRelations(oldID)
// 	relations.Delete()
// 	relations.AnimeID = newID
// 	relations.Save()

// 	// Update anime ID in episode list
// 	episodes, _ := GetAnimeEpisodes(oldID)
// 	episodes.Delete()
// 	episodes.AnimeID = newID
// 	episodes.Save()

// 	// Update anime list items
// 	for animeList := range StreamAnimeLists() {
// 		item := animeList.Find(oldID)

// 		if item != nil {
// 			item.AnimeID = newID
// 			animeList.Save()
// 		}
// 	}

// 	// Update relations pointing to this anime
// 	for relations := range StreamAnimeRelations() {
// 		relation := relations.Find(oldID)

// 		if relation != nil {
// 			relation.AnimeID = newID
// 			relations.Save()
// 		}
// 	}

// 	// Update quotes
// 	for quote := range StreamQuotes() {
// 		if quote.AnimeID == oldID {
// 			quote.AnimeID = newID
// 			quote.Save()
// 		}
// 	}

// 	// Update log entries
// 	for entry := range StreamEditLogEntries() {
// 		switch entry.ObjectType {
// 		case "Anime", "AnimeRelations", "AnimeCharacters", "AnimeEpisodes":
// 			if entry.ObjectID == oldID {
// 				entry.ObjectID = newID
// 				entry.Save()
// 			}
// 		}
// 	}

// 	// Update ignored anime differences
// 	for ignore := range StreamIgnoreAnimeDifferences() {
// 		// ID example: arn:10052|mal:28701|RomajiTitle
// 		arnPart := strings.Split(ignore.ID, "|")[0]
// 		actualID := strings.Split(arnPart, ":")[1]

// 		if actualID == oldID {
// 			DB.Delete("IgnoreAnimeDifference", ignore.ID)
// 			ignore.ID = strings.Replace(ignore.ID, arnPart, "arn:"+newID, 1)
// 			ignore.Save()
// 		}
// 	}

// 	// Update soundtrack tags
// 	for track := range StreamSoundTracks() {
// 		newTags := []string{}
// 		modified := false

// 		for _, tag := range track.Tags {
// 			if strings.HasPrefix(tag, "anime:") {
// 				parts := strings.Split(tag, ":")
// 				id := parts[1]

// 				if id == oldID {
// 					newTags = append(newTags, "anime:"+newID)
// 					modified = true
// 					continue
// 				}
// 			}

// 			newTags = append(newTags, tag)
// 		}

// 		if modified {
// 			track.Tags = newTags
// 			track.Save()
// 		}
// 	}

// 	// Update images on file system
// 	anime.MoveImageFiles(oldID, newID)

// 	// Delete old anime ID
// 	DB.Delete("Anime", oldID)

// 	// Change anime ID and save it
// 	anime.ID = newID
// 	anime.Save()
// }

// // MoveImageFiles ...
// func (anime *Anime) MoveImageFiles(oldID string, newID string) {
// 	if anime.Image.Extension == "" {
// 		return
// 	}

// 	err := os.Rename(
// 		path.Join(Root, "images/anime/original/", oldID+anime.Image.Extension),
// 		path.Join(Root, "images/anime/original/", newID+anime.Image.Extension),
// 	)

// 	if err != nil {
// 		// Don't return the error.
// 		// It's too late to stop the process at this point.
// 		// Instead, log the error.
// 		color.Red(err.Error())
// 	}

// 	os.Rename(
// 		path.Join(Root, "images/anime/large/", oldID+".jpg"),
// 		path.Join(Root, "images/anime/large/", newID+".jpg"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/large/", oldID+"@2.jpg"),
// 		path.Join(Root, "images/anime/large/", newID+"@2.jpg"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/large/", oldID+".webp"),
// 		path.Join(Root, "images/anime/large/", newID+".webp"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/large/", oldID+"@2.webp"),
// 		path.Join(Root, "images/anime/large/", newID+"@2.webp"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/medium/", oldID+".jpg"),
// 		path.Join(Root, "images/anime/medium/", newID+".jpg"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/medium/", oldID+"@2.jpg"),
// 		path.Join(Root, "images/anime/medium/", newID+"@2.jpg"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/medium/", oldID+".webp"),
// 		path.Join(Root, "images/anime/medium/", newID+".webp"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/medium/", oldID+"@2.webp"),
// 		path.Join(Root, "images/anime/medium/", newID+"@2.webp"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/small/", oldID+".jpg"),
// 		path.Join(Root, "images/anime/small/", newID+".jpg"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/small/", oldID+"@2.jpg"),
// 		path.Join(Root, "images/anime/small/", newID+"@2.jpg"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/small/", oldID+".webp"),
// 		path.Join(Root, "images/anime/small/", newID+".webp"),
// 	)

// 	os.Rename(
// 		path.Join(Root, "images/anime/small/", oldID+"@2.webp"),
// 		path.Join(Root, "images/anime/small/", newID+"@2.webp"),
// 	)
// }