package oauth2

import (
	"net/http"
	"net/url"
	"time"

	"secondbit.org/uuid"
)

// AuthorizeRequestType is the type for OAuth param `response_type`
type AuthorizeRequestType string

const (
	CodeAuthRT  AuthorizeRequestType = "code"
	TokenAuthRT                      = "token"
)

// Authorize request information
type AuthorizeRequest struct {
	Type        AuthorizeRequestType
	Client      Client
	Scope       string
	RedirectURI string
	State       string

	// Token expiration in seconds. Change if different from default.
	// If type = TokenAuthRT, this expiration will be for the ACCESS token.
	Expiration int32
}

// Authorization data
type AuthorizeData struct {
	// Client information
	Client Client

	// Authorization code
	Code string

	// Token expiration in seconds
	ExpiresIn int32

	// Requested scope
	Scope string

	// Redirect URI from request
	RedirectURI string

	// State data from request
	State string

	// Date created
	CreatedAt time.Time
}

// IsExpired is true if authorization expired
func (d *AuthorizeData) IsExpired() bool {
	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second).Before(time.Now())
}

// ExpireAt returns the expiration date
func (d *AuthorizeData) ExpireAt() time.Time {
	return d.CreatedAt.Add(time.Duration(d.ExpiresIn) * time.Second)
}

// HandleAuthorizeRequest is the main http.HandlerFunc for handling
// authorization requests
func HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
	r.ParseForm()

	requestType := AuthorizeRequestType(r.Form.Get("response_type"))
	if ctx.Config.AllowedAuthorizeTypes.Exists(requestType) {
		switch requestType {
		case CodeAuthRT:
			handleCodeRequest(w, r, ctx)
			return
		case TokenAuthRT:
			handleTokenRequest(w, r, ctx)
			return
		}
	}
	// TODO: return error
}

func handleCodeRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
	// create the authorization request
	unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
	if err != nil {
		unescapedURI = ""
	}
	ret := &AuthorizeRequest{
		Type:        CodeAuthRT,
		State:       r.Form.Get("state"),
		Scope:       r.Form.Get("scope"),
		RedirectURI: unescapedURI,
		Expiration:  ctx.Config.AuthorizationExpiration,
	}

	// must have a valid client
	id, err := uuid.Parse(r.Form.Get("client_id"))
	if err != nil {
		// TODO: return error
		return
	}
	ret.Client, err = GetClient(id, ctx)
	if err != nil {
		// TODO: return error
		return
	}
	if ret.Client.RedirectURI == "" {
		// TODO: return error
		return
	}

	// check redirect uri
	if ret.RedirectURI == "" {
		ret.RedirectURI = ret.Client.RedirectURI
	}
	if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
		// TODO: return error
		return
	}

	// TODO: do redirect with ret data
}

func handleTokenRequest(w http.ResponseWriter, r *http.Request, ctx Context) {
	// create the authorization request
	unescapedURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
	if err != nil {
		unescapedURI = ""
	}
	ret := &AuthorizeRequest{
		Type:        TokenAuthRT,
		State:       r.Form.Get("state"),
		Scope:       r.Form.Get("scope"),
		RedirectURI: unescapedURI,
		// this type will generate a token directly, use access token expiration instead.
		Expiration: ctx.Config.AccessExpiration,
	}

	// must have a valid client
	id, err := uuid.Parse(r.Form.Get("client_id"))
	if err != nil {
		// TODO: return error
		return
	}
	ret.Client, err = GetClient(id, ctx)
	if err != nil {
		// TODO: return error
		return
	}
	if ret.Client.RedirectURI == "" {
		// TODO: return error
		return
	}

	// check redirect uri
	if ret.RedirectURI == "" {
		ret.RedirectURI = ret.Client.RedirectURI
	}
	if err = ValidateURI(ret.Client.RedirectURI, ret.RedirectURI); err != nil {
		// TODO: return error
	}

	// TODO: redirect with ret information
}

func FinishAuthorizeRequest(w http.ResponseWriter, r *http.Request, ar *AuthorizeRequest, ctx Context) {
	// TODO: check if authorized?
	if ar.Type == TokenAuthRT {
		// TODO: w.SetRedirectFragment(true) was called...

		// generate token directly
		ret := AccessRequest{
			Code:            "",
			Client:          ar.Client,
			RedirectURI:     ar.RedirectURI,
			Scope:           ar.Scope,
			GenerateRefresh: false, // per the RFC, should NOT generate a refresh token in this case
			Expiration:      ar.Expiration,
		}
		// TODO: ret.type was implicit
		// TODO: ret.Authorized was true
		FinishAccessRequest(w, r, ret, ctx)
	} else {
		// generate authorization token
		ret := AuthorizeData{
			Client:      ar.Client,
			CreatedAt:   time.Now(),
			ExpiresIn:   ar.Expiration,
			RedirectURI: ar.RedirectURI,
			State:       ar.State,
			Scope:       ar.Scope,
			Code:        newToken(),
		}

		// save authorization token
		err := saveAuthorize(ret, ctx)
		if err != nil {
			// TODO: return error
			return
		}

		// TODO: redirect with ret.Code and ret.State
	}
}

func loadAuthorize(code string, ctx Context) (AuthorizeData, error) {
	return AuthorizeData{}, nil
}

func saveAuthorize(ret AuthorizeData, ctx Context) error {
	return nil
}

func removeAuthorize(code string, ctx Context) error {
	return nil
}
