package s2s2

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"code.justin.tv/amzn/TwitchS2S2/internal/token"
)

var (
	errNoWWWAuthenticateChallenge      = errors.New("no www-authenticate challenge")
	errInvalidWWWAuthenticateChallenge = errors.New("www-authenticate challenge response did not match '<tokenType> <param>=\"<paramValue>\", <param>=\"<paramValue>\"'")
)

type wwwAuthenticateField string

const (
	wwwAuthenticateFieldIssuer           = wwwAuthenticateField("x-iss")
	wwwAuthenticateFieldRealm            = wwwAuthenticateField("realm")
	wwwAuthenticateFieldScope            = wwwAuthenticateField("scope")
	wwwAuthenticateFieldError            = wwwAuthenticateField("error")
	wwwAuthenticateFieldErrorDescription = wwwAuthenticateField("error_description")
)

// parses www-authenticate headers according to https://tools.ietf.org/html/rfc6750#section-3
func parseWWWAuthenticateChallenge(r *http.Response) (*wwwAuthenticateChallenge, error) {
	raw := r.Header.Get("WWW-Authenticate")
	if raw == "" {
		return nil, errNoWWWAuthenticateChallenge
	}

	wwwAuthenticateChallenge := newWWWAuthenticateChallenge()

	endOfTokenType := strings.Index(raw, " ")
	if endOfTokenType < 0 {
		wwwAuthenticateChallenge.SetChallengeType(raw)
		return wwwAuthenticateChallenge, nil
	}

	wwwAuthenticateChallenge.SetChallengeType(raw[:endOfTokenType])
	raw = raw[endOfTokenType+1:]

	for _, part := range strings.Split(raw, ", ") {
		paramParts := strings.SplitN(strings.TrimSpace(part), "=", 2)
		if len(paramParts) < 2 {
			return nil, errInvalidWWWAuthenticateChallenge
		}

		paramKey := paramParts[0]
		paramValue := paramParts[1]
		if !strings.HasPrefix(paramValue, "\"") || !strings.HasSuffix(paramValue, "\"") {
			return nil, errInvalidWWWAuthenticateChallenge
		}
		wwwAuthenticateChallenge.parameters[wwwAuthenticateField(paramKey)] = paramValue[1 : len(paramValue)-1]
	}

	return wwwAuthenticateChallenge, nil
}

func newWWWAuthenticateChallenge() *wwwAuthenticateChallenge {
	return &wwwAuthenticateChallenge{parameters: make(map[wwwAuthenticateField]string)}
}

type wwwAuthenticateChallenge struct {
	challengeType string
	parameters    map[wwwAuthenticateField]string
}

func (w *wwwAuthenticateChallenge) SetChallengeType(value string) {
	w.challengeType = value
}

func (w *wwwAuthenticateChallenge) Set(key wwwAuthenticateField, value string) {
	w.parameters[key] = value
}

func (w *wwwAuthenticateChallenge) SetRealm(value string) {
	w.Set(wwwAuthenticateFieldRealm, value)
}

func (w *wwwAuthenticateChallenge) Realm() string {
	return w.parameters[wwwAuthenticateFieldRealm]
}

func (w *wwwAuthenticateChallenge) SetScopes(value []string) {
	w.Set(wwwAuthenticateFieldScope, strings.Join(value, " "))
}

func (w *wwwAuthenticateChallenge) Scopes() []string {
	if val, ok := w.parameters[wwwAuthenticateFieldScope]; ok {
		return strings.Split(val, " ")
	}
	return nil
}

func (w *wwwAuthenticateChallenge) Scope() token.Scope {
	if val, ok := w.parameters[wwwAuthenticateFieldScope]; ok {
		return token.ParseScope(val)
	}
	return make(token.Scope)
}

func (w *wwwAuthenticateChallenge) SetIssuer(value string) {
	w.Set(wwwAuthenticateFieldIssuer, value)
}

func (w *wwwAuthenticateChallenge) Issuer() string {
	return w.parameters[wwwAuthenticateFieldIssuer]
}

func (w *wwwAuthenticateChallenge) SetError(errorType, errorDescription string) {
	w.Set(wwwAuthenticateFieldError, errorType)
	w.Set(wwwAuthenticateFieldErrorDescription, errorDescription)
}

func (w *wwwAuthenticateChallenge) ErrorType() authorizationErrorType {
	return authorizationErrorType(w.parameters[wwwAuthenticateFieldError])
}

func (w *wwwAuthenticateChallenge) ErrorDescription() string {
	return w.parameters[wwwAuthenticateFieldErrorDescription]
}

// Returns the WWW-Authenticate header as specified by
// https://tools.ietf.org/html/rfc6750#section-3
func (w *wwwAuthenticateChallenge) HeaderValue() string {
	bs := bytes.NewBufferString(w.challengeType + " ")
	first := true
	for key, value := range w.parameters {
		if first {
			first = false
		} else {
			// bytes.Buffer only panics, doesn't error
			bs.WriteString(", ")
		}
		bs.WriteString(fmt.Sprintf("%s=\"%s\"", key, value))
	}
	return bs.String()
}
