// Package api provides an interface to interact with the Discord REST API. It
// handles rate limiting, as well as authorizing and more.
package api

import (
	"context"
	"net/http"

	"github.com/diamondburned/arikawa/api/rate"
	"github.com/diamondburned/arikawa/utils/httputil"
	"github.com/diamondburned/arikawa/utils/httputil/httpdriver"
)

var (
	BaseEndpoint = "https://discord.com"
	APIVersion   = "6"
	APIPath      = "/api/v" + APIVersion

	Endpoint           = BaseEndpoint + APIPath + "/"
	EndpointGateway    = Endpoint + "gateway"
	EndpointGatewayBot = EndpointGateway + "/bot"
)

var UserAgent = "DiscordBot (https://github.com/diamondburned/arikawa, v0.0.1)"

type Client struct {
	*httputil.Client
	Session
}

func NewClient(token string) *Client {
	return NewCustomClient(token, httputil.NewClient())
}

func NewCustomClient(token string, httpClient *httputil.Client) *Client {
	ses := Session{
		Limiter:   rate.NewLimiter(APIPath),
		Token:     token,
		UserAgent: UserAgent,
	}

	hcl := httpClient.Copy()
	hcl.OnRequest = append(hcl.OnRequest, ses.InjectRequest)
	hcl.OnResponse = append(hcl.OnResponse, ses.OnResponse)

	return &Client{
		Client:  hcl,
		Session: ses,
	}
}

// WithContext returns a shallow copy of Client with the given context. It's
// used for method timeouts and such. This method is thread-safe.
func (c *Client) WithContext(ctx context.Context) *Client {
	return &Client{
		Client:  c.Client.WithContext(ctx),
		Session: c.Session,
	}
}

// Session keeps a single session. This is typically wrapped around Client.
type Session struct {
	Limiter *rate.Limiter

	Token     string
	UserAgent string
}

func (s *Session) InjectRequest(r httpdriver.Request) error {
	r.AddHeader(http.Header{
		"Authorization":         {s.Token},
		"User-Agent":            {s.UserAgent},
		"X-RateLimit-Precision": {"millisecond"},
	})

	// Rate limit stuff
	return s.Limiter.Acquire(r.GetContext(), r.GetPath())
}

func (s *Session) OnResponse(r httpdriver.Request, resp httpdriver.Response) error {
	return s.Limiter.Release(r.GetPath(), httpdriver.OptHeader(resp))
}