package main

import (
	"bytes"
	"encoding/json"
	"io"
	"log"
	"net/http"
)

func AniListQuery(body interface{}, login bool) (json.RawMessage, string) {
	reader, _ := json.Marshal(body)
	response, err := http.NewRequest("POST", "https://graphql.anilist.co", bytes.NewBuffer(reader))
	if err != nil {
		log.Printf("Failed at response, %s\n", err)
	}
	if login && (AniListJWT{}) != aniListJwt {
		response.Header.Add("Authorization", "Bearer "+aniListJwt.AccessToken)
	} else if login {
		return nil, "Please login to AniList to make this request"
	}
	response.Header.Add("Content-Type", "application/json")
	response.Header.Add("Accept", "application/json")

	client := &http.Client{}
	res, resErr := client.Do(response)
	if resErr != nil {
		log.Printf("Failed at res, %s\n", err)
	}

	defer res.Body.Close()

	returnedBody, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, "Could not read the returned body."
	}

	return returnedBody, res.Status
}

func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
	user := a.GetAniListLoggedInUser()

	var neededVariables interface{}

	if login {
		neededVariables = struct {
			MediaId  int    `json:"mediaId"`
			UserId   int    `json:"userId"`
			ListType string `json:"listType"`
		}{
			MediaId:  aniId,
			UserId:   user.Data.Viewer.ID,
			ListType: "ANIME",
		}
	} else {
		neededVariables = struct {
			MediaId  int    `json:"mediaId"`
			ListType string `json:"listType"`
		}{
			MediaId:  aniId,
			ListType: "ANIME",
		}
	}
	body := struct {
		Query     string      `json:"query"`
		Variables interface{} `json:"variables"`
	}{
		Query: `
			query($userId: Int, $mediaId: Int, $listType: MediaType) {
			  MediaList(mediaId: $mediaId, userId: $userId, type: $listType) {
				id
				mediaId
				userId
				media {
				  id
				  idMal
				  title {
					romaji
					english
					native
				  }
				  description
				  coverImage {
					large
				  }
				  season
				  seasonYear
				  status
				  episodes
				  nextAiringEpisode {
					airingAt
					timeUntilAiring
					episode
				  }
				  tags{
					id
					name
					description
					rank
					isMediaSpoiler
					isAdult
				  }
				  isAdult
				}
				status
				startedAt{
				  year
				  month
				  day
				}
				completedAt{
				  year
				  month
				  day
				}
				notes
				progress
				score
				repeat
				user {
				  id
				  name
				  avatar {
					large
					medium
				  }
				  statistics {
					anime {
					  count
					  statuses {
						status
						count
					  }
					}
				  }
				}
			  }
			}
	`,
		Variables: neededVariables,
	}

	returnedBody, status := AniListQuery(body, login)
	var post AniListGetSingleAnime

	if status == "404 Not Found" && !login {
		return post
	}

	if status == "404 Not Found" {
		post = a.GetAniListItem(aniId, false)
	}

	err := json.Unmarshal(returnedBody, &post)
	if err != nil {
		log.Printf("Failed at unmarshal, %s\n", err)
	}

	if !login {
		post.Data.MediaList.UserID = user.Data.Viewer.ID
		post.Data.MediaList.Status = ""
		post.Data.MediaList.StartedAt.Year = 0
		post.Data.MediaList.StartedAt.Month = 0
		post.Data.MediaList.StartedAt.Day = 0
		post.Data.MediaList.CompletedAt.Year = 0
		post.Data.MediaList.CompletedAt.Month = 0
		post.Data.MediaList.CompletedAt.Day = 0
		post.Data.MediaList.Notes = ""
		post.Data.MediaList.Progress = 0
		post.Data.MediaList.Score = 0
		post.Data.MediaList.Repeat = 0
		post.Data.MediaList.User.ID = user.Data.Viewer.ID
		post.Data.MediaList.User.Name = user.Data.Viewer.Name
		post.Data.MediaList.User.Avatar.Large = user.Data.Viewer.Avatar.Large
		post.Data.MediaList.User.Avatar.Medium = user.Data.Viewer.Avatar.Medium
		post.Data.MediaList.User.Statistics.Anime.Count = 0
		// This provides an empty array and frees up the memory from the garbage collector
		post.Data.MediaList.User.Statistics.Anime.Statuses = nil
	}

	return post
}

func (a *App) AniListSearch(query string) any {
	type Variables struct {
		Search   string `json:"search"`
		ListType string `json:"listType"`
	}
	body := struct {
		Query     string    `json:"query"`
		Variables Variables `json:"variables"`
	}{
		Query: `
			query ($search: String!, $listType: MediaType) {
				 Page (page: 1, perPage: 100) {
					pageInfo {
					  total
					  currentPage
					  lastPage
					  hasNextPage
					  perPage
					}
					media (search: $search, type: $listType) {
						id						
						idMal
						title {
						  romaji
						  english
						  native
						}
						description
						coverImage {
						  large
						}
						season
						seasonYear
						status
						episodes
						nextAiringEpisode{
						  airingAt
						  timeUntilAiring
						  episode
						}
						tags{
							id
							name
							description
							rank
							isMediaSpoiler
							isAdult
						  }
					    isAdult
					}
				}
			}
		`,
		Variables: Variables{
			Search:   query,
			ListType: "ANIME",
		},
	}
	returnedBody, _ := AniListQuery(body, false)

	var post interface{}
	err := json.Unmarshal(returnedBody, &post)
	if err != nil {
		log.Printf("Failed at unmarshal, %s\n", err)
	}

	return post
}

func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList {
	user := a.GetAniListLoggedInUser()
	type Variables struct {
		Page     int    `json:"page"`
		PerPage  int    `json:"perPage"`
		UserId   int    `json:"userId"`
		ListType string `json:"listType"`
		Status   string `json:"status"`
		Sort     string `json:"sort"`
	}
	body := struct {
		Query     string    `json:"query"`
		Variables Variables `json:"variables"`
	}{
		Query: `
				query(
				  $page: Int
				  $perPage: Int
				  $userId: Int
				  $listType: MediaType
				  $status: MediaListStatus
				  $sort:[MediaListSort]
				) {
				  Page(page: $page, perPage: $perPage) {
					pageInfo {
					  total
					  perPage
					  currentPage
					  lastPage
					  hasNextPage
					}
					mediaList(userId: $userId, type: $listType, status: $status, sort: $sort) {
					  id
					  mediaId
					  userId
					  media {
						id
						idMal
						title {
						  romaji
						  english
						  native
						}
						description
						coverImage {
						  large
						}
						season
						seasonYear
						status
						episodes
						nextAiringEpisode{
						  airingAt
						  timeUntilAiring
						  episode
						}
						tags{
						  id
						  name
						  description
						  rank
						  isMediaSpoiler
						  isAdult
						}
						isAdult
					  }
					  status
					  startedAt {
						year
						month
						day
					  }
					  completedAt {
						year
						month
						day
					  }
					  notes
					  progress
					  score
					  repeat
					  user {
						id
						name
						avatar{
						  large
						  medium
						}
						statistics {
						  anime {
							count
							statuses {
							  status
							  count
							}
						  }
						}
					  }
					}
				  }
				}
	`,
		Variables: Variables{
			Page:     page,
			PerPage:  perPage,
			UserId:   user.Data.Viewer.ID,
			ListType: "ANIME",
			Status:   "CURRENT",
			Sort:     sort,
		},
	}

	returnedBody, status := AniListQuery(body, true)

	var badPost struct {
		Errors []struct {
			Message   string `json:"message"`
			Status    int    `json:"status"`
			Locations []struct {
				Line   int `json:"line"`
				Column int `json:"column"`
			} `json:"locations"`
		} `json:"errors"`
		Data any `json:"data"`
	}
	var post AniListCurrentUserWatchList
	if status == "200 OK" {
		err := json.Unmarshal(returnedBody, &post)
		if err != nil {
			log.Printf("Failed at unmarshal, %s\n", err)
		}
		// Getting the real total, finding the real last page and storing that in the Page info
		statuses := post.Data.Page.MediaList[0].User.Statistics.Anime.Statuses
		var total int
		for _, status := range statuses {
			if status.Status == "CURRENT" {
				total = status.Count
			}
		}

		lastPage := total / perPage

		post.Data.Page.PageInfo.Total = total
		post.Data.Page.PageInfo.LastPage = lastPage
	}

	if status == "403 Forbidden" {
		err := json.Unmarshal(returnedBody, &badPost)
		if err != nil {
			log.Printf("Failed at unmarshal, %s\n", err)
		}
		log.Fatal(badPost.Errors[0].Message)
	}

	return post
}

func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime {
	body := struct {
		Query     string                 `json:"query"`
		Variables AniListUpdateVariables `json:"variables"`
	}{
		Query: `
			mutation(
			  $mediaId:Int, 
			  $progress:Int, 
			  $status:MediaListStatus,
			  $score:Float,
			  $repeat:Int,
			  $notes:String,
			  $startedAt:FuzzyDateInput,
			  $completedAt:FuzzyDateInput,
			){
			  SaveMediaListEntry(
				mediaId:$mediaId, 
				progress:$progress, 
				status:$status,
				score:$score,
				repeat:$repeat,
				notes:$notes,
				startedAt:$startedAt
				completedAt:$completedAt
			  ){   
				id
				mediaId
				userId
				media {
				  id
				  idMal
				  title {
					romaji
					english
					native
				  }
				  description
				  coverImage {
					large
				  }
				  season
				  seasonYear
				  status
				  episodes
				  nextAiringEpisode {
					airingAt
					timeUntilAiring
					episode
				  }
				  isAdult
				}
				status
				startedAt{
				  year
				  month
				  day
				}
				completedAt{
				  year
				  month
				  day
				}
				notes
				progress
				score
				repeat
				user {
				  id
				  name
				  avatar{
					large
					medium
				  }
				  statistics{
					anime{
					  count
					  statuses{
						status
						count
					  }
					}
				  }
				}
			  }
			}
		`,
		Variables: updateBody,
	}

	returnedBody, _ := AniListQuery(body, true)

	var returnedJson AniListUpdateReturn
	err := json.Unmarshal(returnedBody, &returnedJson)
	if err != nil {
		log.Printf("Failed at unmarshal, %s\n", err)
	}

	var post AniListGetSingleAnime

	post.Data.MediaList = returnedJson.Data.SaveMediaListEntry

	return post
}

func (a *App) AniListDeleteEntry(mediaListId int) DeleteAniListReturn {
	type Variables = struct {
		Id int `json:"id"`
	}

	body := struct {
		Query     string    `json:"query"`
		Variables Variables `json:"variables"`
	}{
		Query: `
			mutation(
			  $id:Int,
			){
			  DeleteMediaListEntry(
				id:$id,
			  ){   
				deleted
				}
			}
		`,
		Variables: Variables{
			Id: mediaListId,
		},
	}

	returnedBody, _ := AniListQuery(body, true)

	var post DeleteAniListReturn
	err := json.Unmarshal(returnedBody, &post)
	if err != nil {
		log.Printf("Failed at unmarshal, %s\n", err)
	}

	return post
}