package s2stoken

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

	"code.justin.tv/amzn/TwitchS2S2/internal/s2s2err"

	"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/token"
	"code.justin.tv/video/metrics-middleware/v2/operation"
)

// AccessTokensError is returned on server errors when creating access tokens
type AccessTokensError struct {
	Message string
}

func (err AccessTokensError) Error() string {
	return s2s2err.S2S2ErrorString("error requesting access token: "+err.Message, "AccessTokens")
}

// AccessTokens are OAuth2 tokens returend in the OAuth2 access token request
// and requires a token from the ClientCredentials flow.
//
// See https://tools.ietf.org/html/rfc6749#section-4.4.2
type AccessTokens struct {
	Client            interfaces.HTTPClient
	ClientCredentials token.Tokens
	OIDC              oidciface.OIDCAPI
	OperationStarter  *operation.Starter
}

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

	clientCredentials, err := at.ClientCredentials.Token(ctx, &token.Options{})
	if err != nil {
		return nil, err
	}

	values := make(url.Values)
	if scope := options.Scope().String(); scope != "" {
		values.Set("scope", scope)
	}
	values.Set("grant_type", "client_credentials")

	req, err := http.NewRequest("POST", at.OIDC.Configuration().TokenEndpoint, bytes.NewReader([]byte(values.Encode())))
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Authorization", "Bearer "+clientCredentials.AccessToken)
	req.Header.Set("X-Host", options.Host())

	res, err := at.Client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

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

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

	return &accessToken, nil
}
