// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package http

import (
	"encoding/base64"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"net/http"

	"github.com/hashicorp/go-secure-stdlib/base62"
	"github.com/hashicorp/vault/vault"
)

func handleSysGenerateRootAttempt(core *vault.Core, generateStrategy vault.GenerateRootStrategy) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			handleSysGenerateRootAttemptGet(core, w, r, "")
		case "POST", "PUT":
			handleSysGenerateRootAttemptPut(core, w, r, generateStrategy)
		case "DELETE":
			handleSysGenerateRootAttemptDelete(core, w, r)
		default:
			respondError(w, http.StatusMethodNotAllowed, nil)
		}
	})
}

func handleSysGenerateRootAttemptGet(core *vault.Core, w http.ResponseWriter, r *http.Request, otp string) {
	ctx, cancel := core.GetContext()
	defer cancel()

	// Get the current seal configuration
	barrierConfig, err := core.SealAccess().BarrierConfig(ctx)
	if err != nil {
		respondError(w, http.StatusInternalServerError, err)
		return
	}
	if barrierConfig == nil {
		respondError(w, http.StatusBadRequest, fmt.Errorf("server is not yet initialized"))
		return
	}

	sealConfig := barrierConfig
	if core.SealAccess().RecoveryKeySupported() {
		sealConfig, err = core.SealAccess().RecoveryConfig(ctx)
		if err != nil {
			respondError(w, http.StatusInternalServerError, err)
			return
		}
	}

	// Get the generation configuration
	generationConfig, err := core.GenerateRootConfiguration()
	if err != nil {
		respondError(w, http.StatusInternalServerError, err)
		return
	}

	// Get the progress
	progress, err := core.GenerateRootProgress()
	if err != nil {
		respondError(w, http.StatusInternalServerError, err)
		return
	}
	var otpLength int
	if core.DisableSSCTokens() {
		otpLength = vault.TokenLength + vault.OldTokenPrefixLength
	} else {
		otpLength = vault.TokenLength + vault.TokenPrefixLength
	}

	// Format the status
	status := &GenerateRootStatusResponse{
		Started:   false,
		Progress:  progress,
		Required:  sealConfig.SecretThreshold,
		Complete:  false,
		OTPLength: otpLength,
		OTP:       otp,
	}
	if generationConfig != nil {
		status.Nonce = generationConfig.Nonce
		status.Started = true
		status.PGPFingerprint = generationConfig.PGPFingerprint
	}

	respondOk(w, status)
}

func handleSysGenerateRootAttemptPut(core *vault.Core, w http.ResponseWriter, r *http.Request, generateStrategy vault.GenerateRootStrategy) {
	// Parse the request
	var req GenerateRootInitRequest
	if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil && err != io.EOF {
		respondError(w, http.StatusBadRequest, err)
		return
	}

	var err error
	var genned bool

	switch {
	case len(req.PGPKey) > 0, len(req.OTP) > 0:
	default:
		genned = true
		if core.DisableSSCTokens() {
			req.OTP, err = base62.Random(vault.TokenLength + vault.OldTokenPrefixLength)
		} else {
			req.OTP, err = base62.Random(vault.TokenLength + vault.TokenPrefixLength)
		}
		if err != nil {
			respondError(w, http.StatusInternalServerError, err)
			return
		}
	}

	// Attemptialize the generation
	if err := core.GenerateRootInit(req.OTP, req.PGPKey, generateStrategy); err != nil {
		respondError(w, http.StatusBadRequest, err)
		return
	}

	if genned {
		handleSysGenerateRootAttemptGet(core, w, r, req.OTP)
		return
	}

	handleSysGenerateRootAttemptGet(core, w, r, "")
}

func handleSysGenerateRootAttemptDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) {
	err := core.GenerateRootCancel()
	if err != nil {
		respondError(w, http.StatusInternalServerError, err)
		return
	}
	respondOk(w, nil)
}

func handleSysGenerateRootUpdate(core *vault.Core, generateStrategy vault.GenerateRootStrategy) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Parse the request
		var req GenerateRootUpdateRequest
		if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
			respondError(w, http.StatusBadRequest, err)
			return
		}
		if req.Key == "" {
			respondError(
				w, http.StatusBadRequest,
				errors.New("'key' must be specified in request body as JSON"))
			return
		}

		// Decode the key, which is base64 or hex encoded
		min, max := core.BarrierKeyLength()
		key, err := hex.DecodeString(req.Key)
		// We check min and max here to ensure that a string that is base64
		// encoded but also valid hex will not be valid and we instead base64
		// decode it
		if err != nil || len(key) < min || len(key) > max {
			key, err = base64.StdEncoding.DecodeString(req.Key)
			if err != nil {
				respondError(
					w, http.StatusBadRequest,
					errors.New("'key' must be a valid hex or base64 string"))
				return
			}
		}

		ctx, cancel := core.GetContext()
		defer cancel()

		// Use the key to make progress on root generation
		result, err := core.GenerateRootUpdate(ctx, key, req.Nonce, generateStrategy)
		if err != nil {
			respondError(w, http.StatusBadRequest, err)
			return
		}

		resp := &GenerateRootStatusResponse{
			Complete:       result.Progress == result.Required,
			Nonce:          req.Nonce,
			Progress:       result.Progress,
			Required:       result.Required,
			Started:        true,
			EncodedToken:   result.EncodedToken,
			PGPFingerprint: result.PGPFingerprint,
		}

		if generateStrategy == vault.GenerateStandardRootTokenStrategy {
			resp.EncodedRootToken = result.EncodedToken
		}

		respondOk(w, resp)
	})
}

type GenerateRootInitRequest struct {
	OTP    string `json:"otp"`
	PGPKey string `json:"pgp_key"`
}

type GenerateRootStatusResponse struct {
	Nonce            string `json:"nonce"`
	Started          bool   `json:"started"`
	Progress         int    `json:"progress"`
	Required         int    `json:"required"`
	Complete         bool   `json:"complete"`
	EncodedToken     string `json:"encoded_token"`
	EncodedRootToken string `json:"encoded_root_token"`
	PGPFingerprint   string `json:"pgp_fingerprint"`
	OTP              string `json:"otp"`
	OTPLength        int    `json:"otp_length"`
}

type GenerateRootUpdateRequest struct {
	Nonce string
	Key   string
}
