package v1

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"

	_ "embed"

	"github.com/yusing/go-proxy/agent/pkg/agent"
	"github.com/yusing/go-proxy/agent/pkg/certs"
	config "github.com/yusing/go-proxy/internal/config/types"
	"github.com/yusing/go-proxy/internal/net/gphttp"
	"github.com/yusing/go-proxy/internal/utils/strutils"
)

func NewAgent(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	name := q.Get("name")
	if name == "" {
		gphttp.ClientError(w, gphttp.ErrMissingKey("name"))
		return
	}
	host := q.Get("host")
	if host == "" {
		gphttp.ClientError(w, gphttp.ErrMissingKey("host"))
		return
	}
	portStr := q.Get("port")
	if portStr == "" {
		gphttp.ClientError(w, gphttp.ErrMissingKey("port"))
		return
	}
	port, err := strconv.Atoi(portStr)
	if err != nil || port < 1 || port > 65535 {
		gphttp.ClientError(w, gphttp.ErrInvalidKey("port"))
		return
	}
	hostport := fmt.Sprintf("%s:%d", host, port)
	if _, ok := config.GetInstance().GetAgent(hostport); ok {
		gphttp.ClientError(w, gphttp.ErrAlreadyExists("agent", hostport), http.StatusConflict)
		return
	}
	t := q.Get("type")
	switch t {
	case "docker", "system":
		break
	case "":
		gphttp.ClientError(w, gphttp.ErrMissingKey("type"))
		return
	default:
		gphttp.ClientError(w, gphttp.ErrInvalidKey("type"))
		return
	}

	nightly := strutils.ParseBool(q.Get("nightly"))
	var image string
	if nightly {
		image = agent.DockerImageNightly
	} else {
		image = agent.DockerImageProduction
	}

	ca, srv, client, err := agent.NewAgent()
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}

	var cfg agent.Generator = &agent.AgentEnvConfig{
		Name:    name,
		Port:    port,
		CACert:  ca.String(),
		SSLCert: srv.String(),
	}
	if t == "docker" {
		cfg = &agent.AgentComposeConfig{
			Image:          image,
			AgentEnvConfig: cfg.(*agent.AgentEnvConfig),
		}
	}
	template, err := cfg.Generate()
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}

	gphttp.RespondJSON(w, r, map[string]any{
		"compose": template,
		"ca":      ca,
		"client":  client,
	})
}

func VerifyNewAgent(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	clientPEMData, err := io.ReadAll(r.Body)
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}

	var data struct {
		Host   string        `json:"host"`
		CA     agent.PEMPair `json:"ca"`
		Client agent.PEMPair `json:"client"`
	}

	if err := json.Unmarshal(clientPEMData, &data); err != nil {
		gphttp.ClientError(w, err, http.StatusBadRequest)
		return
	}

	nRoutesAdded, err := config.GetInstance().VerifyNewAgent(data.Host, data.CA, data.Client)
	if err != nil {
		gphttp.ClientError(w, err)
		return
	}

	zip, err := certs.ZipCert(data.CA.Cert, data.Client.Cert, data.Client.Key)
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}

	filename := certs.AgentCertsFilename(data.Host)
	if !strutils.IsValidFilename(filename) {
		gphttp.ClientError(w, gphttp.ErrInvalidKey("host"))
		return
	}

	if err := os.WriteFile(filename, zip, 0600); err != nil {
		gphttp.ServerError(w, r, err)
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write(fmt.Appendf(nil, "Added %d routes", nRoutesAdded))
}
