package rest

import (
	"encoding/json"
	"errors"
	"net/http"
	"time"

	"github.com/gorilla/mux"
	"github.com/ocdp/cluster-service/internal/domain/entity"
	"github.com/ocdp/cluster-service/internal/domain/service"
	"github.com/ocdp/cluster-service/internal/pkg/authz"
)

type WorkspaceHandler struct {
	workspaceService *service.WorkspaceService
}

func NewWorkspaceHandler(workspaceService *service.WorkspaceService) *WorkspaceHandler {
	return &WorkspaceHandler{workspaceService: workspaceService}
}

type createWorkspaceRequest struct {
	Name string `json:"name"`
}

type workspaceResponse struct {
	ID               string `json:"id"`
	Name             string `json:"name"`
	Status           string `json:"status"`
	K8sNamespace     string `json:"k8sNamespace"`
	K8sSAName        string `json:"k8sSaName"`
	DefaultClusterID string `json:"defaultClusterId,omitempty"`
	QuotaCPU         string `json:"quotaCpu,omitempty"`
	QuotaMemory      string `json:"quotaMemory,omitempty"`
	QuotaGPU         string `json:"quotaGpu,omitempty"`
	QuotaGPUMem      string `json:"quotaGpuMemory,omitempty"`
	CreatedBy        string `json:"createdBy"`
	CreatedAt        string `json:"createdAt"`
	UpdatedAt        string `json:"updatedAt"`
}

type bindClusterRequest struct {
	ClusterID string `json:"clusterId"`
}

type kubeconfigRequest struct {
	ClusterID  string `json:"clusterId"`
	TTLSeconds int64  `json:"ttlSeconds"`
}

func (h *WorkspaceHandler) ListWorkspaces(w http.ResponseWriter, r *http.Request) {
	workspaces, err := h.workspaceService.ListWorkspaces(r.Context())
	if err != nil {
		respondServiceError(w, err, "Failed to list workspaces")
		return
	}
	response := make([]workspaceResponse, 0, len(workspaces))
	for _, workspace := range workspaces {
		response = append(response, toWorkspaceResponse(workspace))
	}
	respondJSON(w, http.StatusOK, response)
}

func (h *WorkspaceHandler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
	var req createWorkspaceRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body", err.Error())
		return
	}
	workspace, err := h.workspaceService.CreateWorkspace(r.Context(), req.Name)
	if err != nil {
		respondServiceError(w, err, "Failed to create workspace")
		return
	}
	respondJSON(w, http.StatusCreated, toWorkspaceResponse(workspace))
}

func (h *WorkspaceHandler) InitClusterBinding(w http.ResponseWriter, r *http.Request) {
	workspaceID := mux.Vars(r)["workspace_id"]
	var req bindClusterRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body", err.Error())
		return
	}
	binding, err := h.workspaceService.EnsureClusterBinding(r.Context(), workspaceID, req.ClusterID)
	if err != nil {
		respondServiceError(w, err, "Failed to initialize workspace cluster binding")
		return
	}
	respondJSON(w, http.StatusOK, binding)
}

func (h *WorkspaceHandler) IssueKubeconfig(w http.ResponseWriter, r *http.Request) {
	workspaceID := mux.Vars(r)["workspace_id"]
	var req kubeconfigRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body", err.Error())
		return
	}
	kubeconfig, err := h.workspaceService.IssueKubeconfig(r.Context(), workspaceID, req.ClusterID, time.Duration(req.TTLSeconds)*time.Second)
	if err != nil {
		respondServiceError(w, err, "Failed to issue kubeconfig")
		return
	}
	respondJSON(w, http.StatusOK, map[string]interface{}{
		"kubeconfig": kubeconfig.Kubeconfig,
		"expiresAt":  kubeconfig.ExpiresAt.Format(time.RFC3339),
	})
}

func (h *WorkspaceHandler) IssueCurrentKubeconfig(w http.ResponseWriter, r *http.Request) {
	clusterID := r.URL.Query().Get("clusterId")
	if clusterID == "" {
		clusterID = r.URL.Query().Get("cluster_id")
	}
	h.issueCurrentKubeconfigForCluster(w, r, clusterID)
}

func (h *WorkspaceHandler) IssueClusterKubeconfig(w http.ResponseWriter, r *http.Request) {
	clusterID := mux.Vars(r)["cluster_id"]
	h.issueCurrentKubeconfigForCluster(w, r, clusterID)
}

func (h *WorkspaceHandler) issueCurrentKubeconfigForCluster(w http.ResponseWriter, r *http.Request, clusterID string) {
	kubeconfig, err := h.workspaceService.IssueCurrentKubeconfig(r.Context(), clusterID, 2*time.Hour)
	if err != nil {
		respondServiceError(w, err, "Failed to issue kubeconfig")
		return
	}
	w.Header().Set("Content-Type", "application/x-yaml")
	w.Header().Set("X-OCDP-Kubeconfig-Expires-At", kubeconfig.ExpiresAt.Format(time.RFC3339))
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(kubeconfig.Kubeconfig))
}

func (h *WorkspaceHandler) SuspendWorkspace(w http.ResponseWriter, r *http.Request) {
	workspaceID := mux.Vars(r)["workspace_id"]
	if err := h.workspaceService.SuspendWorkspace(r.Context(), workspaceID); err != nil {
		respondServiceError(w, err, "Failed to suspend workspace")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func toWorkspaceResponse(workspace *entity.Workspace) workspaceResponse {
	return workspaceResponse{
		ID:               workspace.ID,
		Name:             workspace.Name,
		Status:           string(workspace.Status),
		K8sNamespace:     workspace.K8sNamespace,
		K8sSAName:        workspace.K8sSAName,
		DefaultClusterID: workspace.DefaultClusterID,
		QuotaCPU:         workspace.QuotaCPU,
		QuotaMemory:      workspace.QuotaMemory,
		QuotaGPU:         workspace.QuotaGPU,
		QuotaGPUMem:      workspace.QuotaGPUMem,
		CreatedBy:        workspace.CreatedBy,
		CreatedAt:        workspace.CreatedAt.Format(time.RFC3339),
		UpdatedAt:        workspace.UpdatedAt.Format(time.RFC3339),
	}
}

func respondServiceError(w http.ResponseWriter, err error, fallback string) {
	if errors.Is(err, service.ErrQuotaExceeded) {
		respondError(w, http.StatusUnprocessableEntity, "Quota exceeded", err.Error())
		return
	}
	switch err {
	case entity.ErrUnauthorized, authz.ErrUnauthenticated:
		respondError(w, http.StatusUnauthorized, "Unauthorized", err.Error())
	case entity.ErrForbidden, authz.ErrForbidden, entity.ErrUserInactive, entity.ErrWorkspaceSuspended:
		respondError(w, http.StatusForbidden, "Forbidden", err.Error())
	case entity.ErrWorkspaceNamespaceConflict, entity.ErrUserHasInstances, entity.ErrWorkspaceExists, entity.ErrInstanceExists:
		respondError(w, http.StatusConflict, "Conflict", err.Error())
	case entity.ErrProtectedNamespace:
		respondError(w, http.StatusForbidden, "Forbidden", err.Error())
	case entity.ErrClusterNotFound, entity.ErrRegistryNotFound, entity.ErrInstanceNotFound, entity.ErrWorkspaceNotFound:
		respondError(w, http.StatusNotFound, fallback, err.Error())
	default:
		respondError(w, http.StatusBadRequest, fallback, err.Error())
	}
}
