package handlers

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"

	"github.com/k3s-io/k3s/pkg/clientaccess"
	"github.com/k3s-io/k3s/pkg/cluster"
	"github.com/k3s-io/k3s/pkg/daemons/config"
	"github.com/k3s-io/k3s/pkg/passwd"
	"github.com/k3s-io/k3s/pkg/util"
	"github.com/k3s-io/k3s/pkg/version"
	"github.com/sirupsen/logrus"
)

type TokenRotateRequest struct {
	NewToken *string `json:"newToken,omitempty"`
}

func getServerTokenRequest(req *http.Request) (TokenRotateRequest, error) {
	b, err := io.ReadAll(req.Body)
	if err != nil {
		return TokenRotateRequest{}, err
	}
	result := TokenRotateRequest{}
	err = json.Unmarshal(b, &result)
	return result, err
}

func TokenRequest(ctx context.Context, control *config.Control) http.Handler {
	return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPut {
			util.SendError(fmt.Errorf("method not allowed"), resp, req, http.StatusMethodNotAllowed)
			return
		}
		var err error
		sTokenReq, err := getServerTokenRequest(req)
		logrus.Debug("Received token request")
		if err != nil {
			util.SendError(err, resp, req, http.StatusBadRequest)
			return
		}
		if err = tokenRotate(ctx, control, *sTokenReq.NewToken); err != nil {
			util.SendErrorWithID(err, "token", resp, req, http.StatusInternalServerError)
			return
		}
		resp.WriteHeader(http.StatusOK)
	})
}

func WriteToken(token, file, certs string) error {
	if len(token) == 0 {
		return nil
	}

	token, err := clientaccess.FormatToken(token, certs)
	if err != nil {
		return err
	}
	return os.WriteFile(file, []byte(token+"\n"), 0600)
}

func tokenRotate(ctx context.Context, control *config.Control, newToken string) error {
	passwd, err := passwd.Read(control.Runtime.PasswdFile)
	if err != nil {
		return err
	}

	oldToken, found := passwd.Pass("server")
	if !found {
		return fmt.Errorf("server token not found")
	}
	if newToken == "" {
		newToken, err = util.Random(16)
		if err != nil {
			return err
		}
	}

	if newToken, err = util.NormalizeToken(newToken); err != nil {
		return err
	}

	if err := passwd.EnsureUser("server", version.Program+":server", newToken); err != nil {
		return err
	}

	// If the agent token is the same a server, we need to change both
	if agentToken, found := passwd.Pass("node"); found && agentToken == oldToken && control.AgentToken == "" {
		if err := passwd.EnsureUser("node", version.Program+":agent", newToken); err != nil {
			return err
		}
	}

	if err := passwd.Write(control.Runtime.PasswdFile); err != nil {
		return err
	}

	serverTokenFile := filepath.Join(control.DataDir, "token")
	if err := WriteToken("server:"+newToken, serverTokenFile, control.Runtime.ServerCA); err != nil {
		return err
	}

	if err := cluster.RotateBootstrapToken(ctx, control, oldToken); err != nil {
		return err
	}
	control.Token = newToken
	return cluster.Save(ctx, control, true)
}
