package workspace

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/labstack/echo/v4"
	"net/http"
	"tesseract/internal/apierror"
)

type createWorkspaceRequestBody struct {
	ImageID string `json:"imageId"`
	Runtime string `json:"runtime"`
}

type updateWorkspaceRequestBody struct {
	Status       string        `json:"status"`
	PortMappings []portMapping `json:"ports"`
}

const keyCurrentWorkspace = "currentWorkspace"

func fetchAllWorkspaces(c echo.Context) error {
	mgr := workspaceManagerFrom(c)
	workspaces, err := mgr.findAllWorkspaces(c.Request().Context())
	if err != nil {
		return err
	}
	return c.JSON(http.StatusOK, workspaces)
}

func currentWorkspace(c echo.Context) *workspace {
	return c.Get(keyCurrentWorkspace).(*workspace)
}

func currentWorkspaceMiddleware(ignoreMissing bool) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			workspaceName := c.Param("workspaceName")
			if workspaceName == "" || !workspaceNameRegex.MatchString(workspaceName) {
				return echo.NewHTTPError(http.StatusNotFound)
			}

			mgr := workspaceManagerFrom(c)
			workspace, err := mgr.findWorkspace(c.Request().Context(), workspaceName)
			if err != nil {
				if errors.Is(err, errWorkspaceNotFound) {
					if ignoreMissing {
						c.Set(keyCurrentWorkspace, nil)
					} else {
						return echo.NewHTTPError(http.StatusNotFound)
					}
				} else {
					return err
				}
			}
			c.Set(keyCurrentWorkspace, workspace)

			return next(c)
		}
	}
}

func updateOrCreateWorkspace(c echo.Context) error {
	workspace := currentWorkspace(c)
	if workspace == nil {
		return createWorkspace(c, c.Param("workspaceName"))
	}
	return updateWorkspace(c, workspace)
}

func createWorkspace(c echo.Context, workspaceName string) error {
	var body createWorkspaceRequestBody
	if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest)
	}

	mgr := workspaceManagerFrom(c)

	w, err := mgr.createWorkspace(c.Request().Context(), createWorkspaceOptions{
		name:    workspaceName,
		imageID: body.ImageID,
		runtime: body.Runtime,
	})
	if err != nil {
		if errors.Is(err, errImageNotFound) {
			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("no image with id %v exists", body.ImageID))
		}

		var errWorkspaceExists *errWorkspaceExists
		if errors.As(err, &errWorkspaceExists) {
			return apierror.New(http.StatusBadRequest, "WORKSPACE_EXISTS", errWorkspaceExists.message)
		}

		return err
	}

	return c.JSON(http.StatusOK, w)
}

func updateWorkspace(c echo.Context, workspace *workspace) error {
	ctx := c.Request().Context()

	var body updateWorkspaceRequestBody
	err := json.NewDecoder(c.Request().Body).Decode(&body)
	if err != nil {
		return err
	}

	mgr := workspaceManagerFrom(c)

	switch status(body.Status) {
	case statusStopped:
		if err = mgr.stopWorkspace(ctx, workspace); err != nil {
			return err
		}
		break

	case statusRunning:
		if err = mgr.startWorkspace(ctx, workspace); err != nil {
			return err
		}
		break
	}

	if len(body.PortMappings) > 0 {
		if err = mgr.addPortMappings(ctx, workspace, body.PortMappings); err != nil {
			return err
		}
	}

	return c.JSON(http.StatusOK, workspace)
}

func deleteWorkspace(c echo.Context) error {
	workspace := currentWorkspace(c)
	mgr := workspaceManagerFrom(c)
	if err := mgr.deleteWorkspace(c.Request().Context(), workspace); err != nil {
		if errors.Is(err, errWorkspaceNotFound) {
			return echo.NewHTTPError(http.StatusNotFound)
		}
		return err
	}

	return c.NoContent(http.StatusOK)
}

func deleteWorkspacePortMapping(c echo.Context) error {
	workspace := currentWorkspace(c)
	mgr := workspaceManagerFrom(c)

	portName := c.Param("portName")
	if portName == "" {
		return echo.NewHTTPError(http.StatusNotFound)
	}

	var portMapping *portMapping
	for _, m := range workspace.PortMappings {
		if m.Subdomain == portName {
			portMapping = &m
			break
		}
	}
	if portMapping == nil {
		return echo.NewHTTPError(http.StatusNotFound)
	}

	err := mgr.deletePortMapping(c.Request().Context(), workspace, portMapping)
	if err != nil {
		return err
	}

	return c.NoContent(http.StatusOK)
}

func fetchWorkspaceRuntimes(c echo.Context) error {
	mgr := workspaceManagerFrom(c)
	runtimes, err := mgr.findAvailableWorkspaceRuntimes(c.Request().Context())
	if err != nil {
		return err
	}
	return c.JSON(http.StatusOK, runtimes)
}
