package anilist

import (
	"bytes"
	"compress/gzip"
	"errors"
	"fmt"
	"net/http"
	"seanime/internal/util"
	"strconv"
	"time"

	"github.com/goccy/go-json"
	"github.com/rs/zerolog"
)

func CustomQuery(body map[string]interface{}, logger *zerolog.Logger, token string) (data interface{}, err error) {
	bodyBytes, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}
	return customQuery(bodyBytes, logger, token)
}

func customQuery(body []byte, logger *zerolog.Logger, token ...string) (data interface{}, err error) {

	var rlRemainingStr string

	reqTime := time.Now()
	defer func() {
		timeSince := time.Since(reqTime)
		formattedDur := timeSince.Truncate(time.Millisecond).String()
		if err != nil {
			logger.Error().Str("duration", formattedDur).Str("rlr", rlRemainingStr).Err(err).Msg("anilist: Failed Request")
		} else {
			if timeSince > 600*time.Millisecond {
				logger.Warn().Str("rtt", formattedDur).Str("rlr", rlRemainingStr).Msg("anilist: Long Request")
			} else {
				logger.Trace().Str("rtt", formattedDur).Str("rlr", rlRemainingStr).Msg("anilist: Successful Request")
			}
		}
	}()

	defer util.HandlePanicInModuleThen("api/anilist/custom_query", func() {
		err = errors.New("panic in customQuery")
	})

	client := http.DefaultClient

	var req *http.Request
	req, err = http.NewRequest("POST", "https://graphql.anilist.co", bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	if len(token) > 0 && token[0] != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token[0]))
	}

	// Send request
	retryCount := 2

	var resp *http.Response
	for i := 0; i < retryCount; i++ {

		// Reset response body for retry
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}

		// Recreate the request body if it was read in a previous attempt
		if req.GetBody != nil {
			newBody, err := req.GetBody()
			if err != nil {
				return nil, fmt.Errorf("failed to get request body: %w", err)
			}
			req.Body = newBody
		}

		resp, err = client.Do(req)
		if err != nil {
			return nil, fmt.Errorf("request failed: %w", err)
		}

		rlRemainingStr = resp.Header.Get("X-Ratelimit-Remaining")
		rlRetryAfterStr := resp.Header.Get("Retry-After")
		rlRetryAfter, err := strconv.Atoi(rlRetryAfterStr)
		if err == nil {
			logger.Warn().Msgf("anilist: Rate limited, retrying in %d seconds", rlRetryAfter+1)
			select {
			case <-time.After(time.Duration(rlRetryAfter+1) * time.Second):
				continue
			}
		}

		if rlRemainingStr == "" {
			select {
			case <-time.After(5 * time.Second):
				continue
			}
		}

		break
	}

	defer resp.Body.Close()

	if resp.Header.Get("Content-Encoding") == "gzip" {
		resp.Body, err = gzip.NewReader(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("gzip decode failed: %w", err)
		}
	}

	var res interface{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	var ok bool

	reqErrors, ok := res.(map[string]interface{})["errors"].([]interface{})

	if ok && len(reqErrors) > 0 {
		firstError, foundErr := reqErrors[0].(map[string]interface{})
		if foundErr {
			return nil, errors.New(firstError["message"].(string))
		}
	}

	data, ok = res.(map[string]interface{})["data"]
	if !ok {
		return nil, errors.New("failed to parse data")
	}

	return data, nil
}
