// package mbz provides functions for interacting with the musicbrainz api
package mbz

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"

	"github.com/gabehf/koito/internal/cfg"
	"github.com/gabehf/koito/internal/logger"
	"github.com/gabehf/koito/queue"
	"github.com/google/uuid"
)

type MusicBrainzArea struct {
	Name           string   `json:"name"`
	Iso3166_1Codes []string `json:"iso-3166-1-codes"`
}

type MusicBrainzClient struct {
	url          string
	userAgent    string
	requestQueue *queue.RequestQueue
}

type MusicBrainzCaller interface {
	GetArtistPrimaryAliases(ctx context.Context, id uuid.UUID) ([]string, error)
	GetReleaseTitles(ctx context.Context, RGID uuid.UUID) ([]string, error)
	GetTrack(ctx context.Context, id uuid.UUID) (*MusicBrainzTrack, error)
	GetReleaseGroup(ctx context.Context, id uuid.UUID) (*MusicBrainzReleaseGroup, error)
	GetRelease(ctx context.Context, id uuid.UUID) (*MusicBrainzRelease, error)
	Shutdown()
}

func NewMusicBrainzClient() *MusicBrainzClient {
	ret := new(MusicBrainzClient)
	ret.url = cfg.MusicBrainzUrl()
	ret.userAgent = cfg.UserAgent()
	ret.requestQueue = queue.NewRequestQueue(cfg.MusicBrainzRateLimit(), cfg.MusicBrainzRateLimit())
	return ret
}

func (c *MusicBrainzClient) Shutdown() {
	c.requestQueue.Shutdown()
}

func (c *MusicBrainzClient) getEntity(ctx context.Context, fmtStr string, id uuid.UUID, result any) error {
	l := logger.FromContext(ctx)
	url := fmt.Sprintf(fmtStr, c.url, id.String())
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		l.Err(err).Msg("Failed to build MusicBrainz request")
		return fmt.Errorf("getEntity: %w", err)
	}
	l.Debug().Msg("Adding MusicBrainz request to queue")
	body, err := c.queue(ctx, req)
	if err != nil {
		l.Err(err).Msg("MusicBrainz request failed")
		return fmt.Errorf("getEntity: %w", err)
	}

	err = json.Unmarshal(body, result)
	if err != nil {
		l.Err(err).Str("body", string(body)).Msg("Failed to unmarshal MusicBrainz response body")
		return fmt.Errorf("getEntity: %w", err)
	}

	return nil
}

func (c *MusicBrainzClient) queue(ctx context.Context, req *http.Request) ([]byte, error) {
	l := logger.FromContext(ctx)
	req.Header.Set("User-Agent", c.userAgent)
	req.Header.Set("Accept", "application/json")

	resultChan := c.requestQueue.Enqueue(func(client *http.Client, done chan<- queue.RequestResult) {
		resp, err := client.Do(req)
		if err != nil {
			l.Err(err).Str("url", req.RequestURI).Msg("Failed to contact MusicBrainz")
			done <- queue.RequestResult{Err: err}
			return
		} else if resp.StatusCode >= 300 || resp.StatusCode < 200 {
			err = fmt.Errorf("recieved non-ok status from MusicBrainz: %s", resp.Status)
			done <- queue.RequestResult{Body: nil, Err: err}
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		done <- queue.RequestResult{Body: body, Err: err}
	})

	result := <-resultChan
	return result.Body, result.Err
}
