// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package oauth2

import (
	"errors"
	"net/http"
	"time"

	"github.com/drone/go-login/login"
	"github.com/drone/go-login/login/logger"
)

// Handler returns a Handler that runs h at the completion
// of the oauth2 authorization flow.
func Handler(h http.Handler, c *Config) http.Handler {
	return &handler{next: h, conf: c, logs: c.Logger}
}

type handler struct {
	conf *Config
	next http.Handler
	logs logger.Logger
}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	// checks for the error query parameter in the request.
	// If non-empty, write to the context and proceed with
	// the next http.Handler in the chain.
	if erro := r.FormValue("error"); erro != "" {
		h.logger().Errorf("oauth: authorization error: %s", erro)
		ctx = login.WithError(ctx, errors.New(erro))
		h.next.ServeHTTP(w, r.WithContext(ctx))
		return
	}

	// checks for the code query parameter in the request
	// If empty, redirect to the authorization endpoint.
	code := r.FormValue("code")
	if len(code) == 0 {
		state := createState(w)
		http.Redirect(w, r, h.conf.authorizeRedirect(state), 303)
		return
	}

	// checks for the state query parameter in the requet.
	// If empty, write the error to the context and proceed
	// with the next http.Handler in the chain.
	state := r.FormValue("state")
	deleteState(w)
	if err := validateState(r, state); err != nil {
		h.logger().Errorln("oauth: invalid or missing state")
		ctx = login.WithError(ctx, err)
		h.next.ServeHTTP(w, r.WithContext(ctx))
		return
	}

	// requests the access_token and refresh_token from the
	// authorization server. If an error is encountered,
	// write the error to the context and prceed with the
	// next http.Handler in the chain.
	source, err := h.conf.exchange(code, state)
	if err != nil {
		h.logger().Errorf("oauth: cannot exchange code: %s: %s", code, err)
		ctx = login.WithError(ctx, err)
		h.next.ServeHTTP(w, r.WithContext(ctx))
		return
	}

	// converts the oauth2 token type to the internal Token
	// type and attaches to the context.
	ctx = login.WithToken(ctx, &login.Token{
		Access:  source.AccessToken,
		Refresh: source.RefreshToken,
		Expires: time.Now().UTC().Add(
			time.Duration(source.Expires) * time.Second,
		),
	})

	h.next.ServeHTTP(w, r.WithContext(ctx))
}

func (h *handler) logger() logger.Logger {
	if h.logs == nil {
		return logger.Discard()
	}
	return h.logs
}
