package release

import (
	"encoding/json"
	"net/http"
	"strings"

	"github.com/porter-dev/porter/api/server/authz"
	"github.com/porter-dev/porter/api/server/handlers"
	"github.com/porter-dev/porter/api/server/shared"
	"github.com/porter-dev/porter/api/server/shared/apierrors"
	"github.com/porter-dev/porter/api/server/shared/config"
	"github.com/porter-dev/porter/api/server/shared/websocket"
	"github.com/porter-dev/porter/api/types"
	"github.com/porter-dev/porter/internal/models"
	"github.com/porter-dev/porter/internal/templater/parser"
	"github.com/stefanmcshane/helm/pkg/release"
)

type StreamFormHandler struct {
	handlers.PorterHandlerReadWriter
	authz.KubernetesAgentGetter
}

func NewStreamFormHandler(
	config *config.Config,
	decoderValidator shared.RequestDecoderValidator,
	writer shared.ResultWriter,
) *StreamFormHandler {
	return &StreamFormHandler{
		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
	}
}

func getStreamWriter(rw *websocket.WebsocketSafeReadWriter) func(val map[string]interface{}) error {
	return func(val map[string]interface{}) error {
		// parse value into json
		bytes, err := json.Marshal(val)
		if err != nil {
			return err
		}

		_, err = rw.Write(bytes)
		return err
	}
}

func (c *StreamFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	helmRelease, _ := r.Context().Value(types.ReleaseScope).(*release.Release)
	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
	safeRW := r.Context().Value(types.RequestCtxWebsocketKey).(*websocket.WebsocketSafeReadWriter)

	request := &types.StreamCRDRequest{}

	if ok := c.DecodeAndValidate(w, r, request); !ok {
		return
	}

	// look for the form using the dynamic client
	dynClient, err := c.GetDynamicClient(r, cluster)
	if err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}

	parserDef := &parser.ClientConfigDefault{
		DynamicClient: dynClient,
		HelmChart:     helmRelease.Chart,
		HelmRelease:   helmRelease,
	}

	var formData []byte

	for _, file := range helmRelease.Chart.Files {
		if strings.Contains(file.Name, "form.yaml") {
			formData = file.Data
			break
		}
	}

	// if form data isn't found, look for common charts
	if formData == nil {
		// for now just case by name
		if helmRelease.Chart.Name() == "cert-manager" {
			formData = []byte(certManagerForm)
		}
	}

	stopper := make(chan struct{})
	errorchan := make(chan error)
	defer close(stopper)

	go func() {
		// listens for websocket closing handshake
		for {
			if _, _, err := safeRW.ReadMessage(); err != nil {
				errorchan <- nil
				return
			}
		}
	}()

	onData := getStreamWriter(safeRW)

	err = parser.FormStreamer(parserDef, formData, "", &types.FormContext{
		Type: "cluster",
		Config: map[string]string{
			"group":    request.Group,
			"resource": request.Resource,
			"version":  request.Version,
		},
	}, onData, stopper)

	if err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}

	for {
		select {
		case err := <-errorchan:
			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
			return
		}
	}
}
