package api

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

	"github.com/sdp/control-plane/internal/store"
)

type sandboxReq struct {
	Name            string                   `json:"name"`
	GatewayBranch   string                   `json:"gatewayBranch"`
	GatewayEnvID    string                   `json:"gatewayEnvId"`
	GatewayHostPort int                      `json:"gatewayHostPort"`
	Services        []store.SandboxService   `json:"services"`
}

type cloneReq struct {
	TemplateID string `json:"templateId"`
	Name       string `json:"name"`
}

func (s *Server) handleSandboxes(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		sbs, err := s.st.ListSandboxes()
		if err != nil {
			writeErr(w, http.StatusInternalServerError, err.Error())
			return
		}
		if sbs == nil {
			sbs = []store.Sandbox{}
		}
		writeJSON(w, http.StatusOK, sbs)
	case http.MethodPost:
		var body sandboxReq
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			http.Error(w, "bad json", http.StatusBadRequest)
			return
		}
		if body.Name == "" {
			writeErr(w, http.StatusBadRequest, "name required")
			return
		}
		sb := store.Sandbox{
			ID:              newID(),
			Name:            body.Name,
			GatewayBranch:   body.GatewayBranch,
			GatewayEnvID:    body.GatewayEnvID,
			GatewayHostPort: body.GatewayHostPort,
			Services:        body.Services,
		}
		if err := s.st.CreateSandbox(sb); err != nil {
			writeErr(w, http.StatusBadRequest, err.Error())
			return
		}
		writeJSON(w, http.StatusOK, sb)
	default:
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
	}
}

func (s *Server) handleSandboxByID(w http.ResponseWriter, r *http.Request) {
	rest := strings.TrimPrefix(r.URL.Path, "/api/sandboxes/")
	parts := strings.Split(rest, "/")
	id := parts[0]
	if id == "" {
		http.Error(w, "not found", http.StatusNotFound)
		return
	}
	// /api/sandboxes/{id}/deploy/{repo}
	if len(parts) == 3 && parts[1] == "deploy" {
		s.handleSandboxDeploy(w, r, id, parts[2])
		return
	}
	if len(parts) != 1 {
		http.Error(w, "not found", http.StatusNotFound)
		return
	}
	switch r.Method {
	case http.MethodGet:
		sb, err := s.st.GetSandbox(id)
		if err == store.ErrNotFound {
			http.Error(w, "not found", http.StatusNotFound)
			return
		}
		if err != nil {
			writeErr(w, http.StatusInternalServerError, err.Error())
			return
		}
		// include most recent deploy per service
		for i := range sb.Services {
			d, _ := s.st.LatestDeploymentBySandboxService(sb.ID, sb.Services[i].Repo)
			if d != nil {
				sb.Services[i].Branch = d.Branch
			}
		}
		writeJSON(w, http.StatusOK, sb)
	case http.MethodPut:
		var body sandboxReq
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			http.Error(w, "bad json", http.StatusBadRequest)
			return
		}
		sb := store.Sandbox{
			ID:              id,
			Name:            body.Name,
			GatewayBranch:   body.GatewayBranch,
			GatewayEnvID:    body.GatewayEnvID,
			GatewayHostPort: body.GatewayHostPort,
			Services:        body.Services,
		}
		if err := s.st.UpdateSandbox(sb); err == store.ErrNotFound {
			http.Error(w, "not found", http.StatusNotFound)
			return
		} else if err != nil {
			writeErr(w, http.StatusBadRequest, err.Error())
			return
		}
		writeJSON(w, http.StatusOK, sb)
	case http.MethodDelete:
		if err := s.st.DeleteSandbox(id); err == store.ErrNotFound {
			http.Error(w, "not found", http.StatusNotFound)
			return
		} else if err != nil {
			writeErr(w, http.StatusInternalServerError, err.Error())
			return
		}
		writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
	default:
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
	}
}

func (s *Server) handleCloneSandbox(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "POST only", http.StatusMethodNotAllowed)
		return
	}
	var body cloneReq
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "bad json", http.StatusBadRequest)
		return
	}
	if body.TemplateID == "" || body.Name == "" {
		writeErr(w, http.StatusBadRequest, "templateId and name required")
		return
	}
	sb := store.Sandbox{ID: newID(), Name: body.Name}
	if err := s.st.CloneTemplateIntoSandbox(body.TemplateID, sb); err == store.ErrNotFound {
		http.Error(w, "template not found", http.StatusNotFound)
		return
	} else if err != nil {
		writeErr(w, http.StatusBadRequest, err.Error())
		return
	}
	writeJSON(w, http.StatusOK, sb)
}

type sandboxDeployReq struct {
	Branch   string            `json:"branch"`
	Env      map[string]string `json:"env,omitempty"`
	EnvID    string            `json:"envId,omitempty"`
	Username string            `json:"username"`
	Password string            `json:"password"`
}

// handleSandboxDeploy deploys one service inside a sandbox. The
// sandbox's host_port is used unless the request overrides it. The
// sandbox's env is merged with the request's env.
func (s *Server) handleSandboxDeploy(w http.ResponseWriter, r *http.Request, sandboxID, repo string) {
	if r.Method != http.MethodPost {
		http.Error(w, "POST only", http.StatusMethodNotAllowed)
		return
	}
	var body sandboxDeployReq
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "bad json", http.StatusBadRequest)
		return
	}
	if body.Branch == "" {
		writeErr(w, http.StatusBadRequest, "branch required")
		return
	}
	sb, err := s.st.GetSandbox(sandboxID)
	if err == store.ErrNotFound {
		http.Error(w, "sandbox not found", http.StatusNotFound)
		return
	}
	if err != nil {
		writeErr(w, http.StatusInternalServerError, err.Error())
		return
	}
	var svc *store.SandboxService
	for i := range sb.Services {
		if sb.Services[i].Repo == repo {
			svc = &sb.Services[i]
			break
		}
	}
	if svc == nil {
		writeErr(w, http.StatusBadRequest, "repo not in sandbox")
		return
	}
	env := body.Env
	if env == nil {
		env = map[string]string{}
	}
	if svc.EnvID != "" {
		if e, err := s.st.GetEnvironment(svc.EnvID); err == nil {
			for k, v := range e.Values {
				env[k] = v
			}
		}
	}
	if body.EnvID != "" {
		if e, err := s.st.GetEnvironment(body.EnvID); err == nil {
			for k, v := range e.Values {
				env[k] = v
			}
		}
	}

	node, _ := s.resolveNode(repo, sandboxID)
	s.agents.mu.RLock()
	connected := s.agents.conns[node]
	s.agents.mu.RUnlock()
	if !connected {
		writeErr(w, http.StatusServiceUnavailable, "agent "+node+" not connected")
		return
	}

	user := s.userFromContext(r)
	id := newID()
	if err := s.st.StartDeploymentInSandbox(id, sandboxID, repo, body.Branch, user, svc.HostPort); err != nil {
		writeErr(w, http.StatusInternalServerError, err.Error())
		return
	}

	req := map[string]any{
		"deploymentId": id,
		"sandboxId":    sandboxID,
		"repository":   repo,
		"branch":       body.Branch,
		"hostPort":     svc.HostPort,
		"env":          env,
		"username":     body.Username,
		"password":     body.Password,
	}
	payload, _ := json.Marshal(map[string]any{"op": "deploy", "data": req})
	if !s.hub.SendToAgent(node, payload) {
		writeErr(w, http.StatusServiceUnavailable, "agent buffer full")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"id": id})
}
