package s2stoken

import (
	"context"
	"encoding/json"
	"net/http"
	"net/url"
	"time"

	"code.justin.tv/amzn/TwitchS2S2/c7s"
	"code.justin.tv/amzn/TwitchS2S2/internal/interfaces"
	"code.justin.tv/amzn/TwitchS2S2/internal/oidc/oidciface"
	"code.justin.tv/amzn/TwitchS2S2/internal/opwrap"
	"code.justin.tv/amzn/TwitchS2S2/internal/s2s2err"
	"code.justin.tv/amzn/TwitchS2S2/internal/token"
	"code.justin.tv/video/metrics-middleware/v2/operation"
)

// AssertionsError is return on server errors when creating assertions
type AssertionsError struct {
	Message string
}

func (err AssertionsError) Error() string {
	return s2s2err.S2S2ErrorString("error requesting assertions: "+err.Message, "Assertions")
}

// Assertions uses the assertion flow to retrieve tokens to use in the client
// credentials flow. Client used to send request should be SigV4 signed.
//
// See https://tools.ietf.org/html/rfc7521
type Assertions struct {
	Config           *c7s.Config
	OIDC             oidciface.OIDCAPI
	OperationStarter *operation.Starter
	SigV4Client      interfaces.HTTPClient
	// this is either c7s.Config.ClientServiceURI or c7s.Config.ClientServiceName
	// in URI form.
	ClientServiceURI string
}

// Token implements token.Tokens
func (a *Assertions) Token(ctx context.Context, options *token.Options) (_ *token.Token, err error) {
	ctx, op := a.OperationStarter.StartOp(ctx, opwrap.GetAssertion)
	defer opwrap.EndWithError(op, err)

	req, err := http.NewRequest("GET", a.OIDC.Configuration().AuthorizationEndpoint, nil)
	if err != nil {
		return nil, err
	}

	req.URL.RawQuery = url.Values{
		"host":              []string{a.Config.Issuer},
		"response_type":     []string{"assertion"},
		"twitch_s2s_client": []string{a.ClientServiceURI},
	}.Encode()

	res, err := a.SigV4Client.Do(req.WithContext(ctx))
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return nil, &AssertionsError{Message: parseAuthServerError(res.Body)}
	}

	var assertion token.Token
	if err := json.NewDecoder(res.Body).Decode(&assertion); err != nil {
		return nil, err
	}
	assertion.Issued = time.Now().UTC()
	return &assertion, nil
}
