// Package handler provides HTTP handlers for the OpenID Connect Provider.
package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"testing"

	"github.com/int128/kubelogin/integration_test/oidcserver/service"
)

func Register(t *testing.T, mux *http.ServeMux, provider service.Provider) {
	h := &Handlers{t, provider}
	mux.HandleFunc("GET /.well-known/openid-configuration", h.Discovery)
	mux.HandleFunc("GET /certs", h.GetCertificates)
	mux.HandleFunc("GET /auth", h.AuthenticateCode)
	mux.HandleFunc("POST /token", h.Exchange)
}

// Handlers provides HTTP handlers for the OpenID Connect Provider.
// You need to implement the Provider interface.
// Note that this skips some security checks and is only for testing.
type Handlers struct {
	t        *testing.T
	provider service.Provider
}

func (h *Handlers) handleError(w http.ResponseWriter, r *http.Request, f func() error) {
	err := f()
	if err == nil {
		return
	}
	if errResp := new(service.ErrorResponse); errors.As(err, &errResp) {
		h.t.Logf("400 %s %s: %s", r.Method, r.RequestURI, err)
		w.Header().Add("Content-Type", "application/json")
		w.WriteHeader(400)
		e := json.NewEncoder(w)
		if err := e.Encode(errResp); err != nil {
			h.t.Errorf("idp/handler: could not write the response: %s", err)
		}
		return
	}
	h.t.Logf("500 %s %s: %s", r.Method, r.RequestURI, err)
	http.Error(w, err.Error(), 500)
}

func (h *Handlers) Discovery(w http.ResponseWriter, r *http.Request) {
	h.handleError(w, r, func() error {
		discoveryResponse := h.provider.Discovery()
		w.Header().Add("Content-Type", "application/json")
		e := json.NewEncoder(w)
		if err := e.Encode(discoveryResponse); err != nil {
			return fmt.Errorf("could not render json: %w", err)
		}
		return nil
	})
}

func (h *Handlers) GetCertificates(w http.ResponseWriter, r *http.Request) {
	h.handleError(w, r, func() error {
		certificatesResponse := h.provider.GetCertificates()
		w.Header().Add("Content-Type", "application/json")
		e := json.NewEncoder(w)
		if err := e.Encode(certificatesResponse); err != nil {
			return fmt.Errorf("could not render json: %w", err)
		}
		return nil
	})
}

func (h *Handlers) AuthenticateCode(w http.ResponseWriter, r *http.Request) {
	h.handleError(w, r, func() error {
		q := r.URL.Query()
		redirectURI, state := q.Get("redirect_uri"), q.Get("state")
		code, err := h.provider.AuthenticateCode(service.AuthenticationRequest{
			RedirectURI:         redirectURI,
			State:               state,
			Scope:               q.Get("scope"),
			Nonce:               q.Get("nonce"),
			CodeChallenge:       q.Get("code_challenge"),
			CodeChallengeMethod: q.Get("code_challenge_method"),
			RawQuery:            q,
		})
		if err != nil {
			return fmt.Errorf("authentication error: %w", err)
		}
		redirectTo, err := url.Parse(redirectURI)
		if err != nil {
			return fmt.Errorf("invalid redirect_uri: %w", err)
		}
		redirectTo.RawQuery = url.Values{"state": {state}, "code": {code}}.Encode()
		http.Redirect(w, r, redirectTo.String(), http.StatusFound)
		return nil
	})
}

func (h *Handlers) Exchange(w http.ResponseWriter, r *http.Request) {
	h.handleError(w, r, func() error {
		if err := r.ParseForm(); err != nil {
			return fmt.Errorf("could not parse the form: %w", err)
		}
		grantType := r.Form.Get("grant_type")
		switch grantType {
		case "authorization_code":
			tokenResponse, err := h.provider.Exchange(service.TokenRequest{
				Code:         r.Form.Get("code"),
				CodeVerifier: r.Form.Get("code_verifier"),
			})
			if err != nil {
				return fmt.Errorf("token request error: %w", err)
			}
			w.Header().Add("Content-Type", "application/json")
			e := json.NewEncoder(w)
			if err := e.Encode(tokenResponse); err != nil {
				return fmt.Errorf("could not render json: %w", err)
			}
		case "password":
			// 4.3. Resource Owner Password Credentials Grant
			// https://tools.ietf.org/html/rfc6749#section-4.3
			username, password, scope := r.Form.Get("username"), r.Form.Get("password"), r.Form.Get("scope")
			tokenResponse, err := h.provider.AuthenticatePassword(username, password, scope)
			if err != nil {
				return fmt.Errorf("authentication error: %w", err)
			}
			w.Header().Add("Content-Type", "application/json")
			e := json.NewEncoder(w)
			if err := e.Encode(tokenResponse); err != nil {
				return fmt.Errorf("could not render json: %w", err)
			}
		case "refresh_token":
			// 12.1. Refresh Request
			// https://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken
			refreshToken := r.Form.Get("refresh_token")
			tokenResponse, err := h.provider.Refresh(refreshToken)
			if err != nil {
				return fmt.Errorf("token refresh error: %w", err)
			}
			w.Header().Add("Content-Type", "application/json")
			e := json.NewEncoder(w)
			if err := e.Encode(tokenResponse); err != nil {
				return fmt.Errorf("could not render json: %w", err)
			}
		default:
			// 5.2. Error Response
			// https://tools.ietf.org/html/rfc6749#section-5.2
			return &service.ErrorResponse{
				Code:        "invalid_grant",
				Description: fmt.Sprintf("unknown grant_type %s", grantType),
			}
		}
		return nil
	})
}
