package anilist

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
)

type Api struct {
	AccessToken string
}

func NewApi(accessToken string) *Api {
	return &Api{AccessToken: accessToken}
}

type queryObj struct {
	Query string      `json:"query"`
	Vars  interface{} `json:"variables"`
}

type responseObj[T any] struct {
	Data T `json:"data"`
}

func request[T any](api *Api, query string, vars map[string]interface{}, respObj *responseObj[T]) error {
	q := &queryObj{
		Query: query,
		Vars:  vars,
	}

	queryData, err := json.Marshal(q)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", "https://graphql.anilist.co/", bytes.NewReader(queryData))
	if err != nil {
		return err
	}

	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", "Bearer "+api.AccessToken)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	//data, _ := ioutil.ReadAll(resp.Body)
	//fmt.Println(string(data))

	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(respObj)
	if err != nil {
		return err
	}

	return nil
}

func requestPaged[R any](api *Api, ctx context.Context, query string, vars map[string]interface{}, respObj *responseObj[*page[R]], onError func(error)) *Cursor[R] {
	resp := responseObj[struct {
		Page *page[R] `json:"Page"`
	}]{}

	var cancelFunc context.CancelFunc
	ctx, cancelFunc = context.WithCancel(ctx)

	out := make(chan *R, 50)

	go func() {
		defer close(out)
		defer cancelFunc()

		vars["page"] = 0

		for {
			if p, ok := vars["page"].(int); ok {
				vars["page"] = p + 1
			}

			err := request(api, query, vars, &resp)
			if err != nil {
				if onError != nil {
					onError(err)
				}
				return
			}

			if resp.Data.Page == nil {
				return
			}

			for _, value := range resp.Data.Page.Data() {
				value := value
				select {
				case out <- &value:
				case <-ctx.Done():
					return
				}
			}

			if resp.Data.Page.PageInfo.CurrentPage == resp.Data.Page.PageInfo.LastPage {
				return
			}
		}
	}()

	return &Cursor[R]{
		Chan:       out,
		ctx:        ctx,
		cancelFunc: cancelFunc,
	}
}