// Package api wires the HTTP endpoints. Kept on net/http — no router lib
// for a handful of endpoints, stdlib mux is plenty.
//
// Slice 2 adds sandboxes, templates, environments, routes, port
// allocation, and a real agent-mediated auth path. The legacy
// /api/deployments POST is kept for ad-hoc deploys (no sandbox) and a
// new /api/sandboxes/{id}/services/{repo}/deploy adds the
// sandbox-scoped variant that picks the right port and env.
package api

import (
	"crypto/rand"
	"encoding/hex"
	"net/http"
	"strings"
	"sync"
	"time"

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

type Server struct {
	st     *store.Store
	hub    *ws.Hub
	agents *AgentRegistry
	sess   *Sessions
	// gatewayRepo is the repo the control plane uses to validate
	// Bitbucket creds at login. Defaults to "api-gateway".
	gatewayRepo string
	// microVMIP / gatewayVMIP are the host IPs the dashboard shows
	// when a user picks a "local stand-in" for a microservice route.
	// Defaults match the company infra (172.18.136.92 / 172.18.139.186).
	microVMIP   string
	gatewayVMIP string
}

type AgentRegistry struct {
	mu    sync.RWMutex
	conns map[string]bool // nodeIDs currently connected over WS
}

func New(st *store.Store, hub *ws.Hub) *Server {
	return &Server{
		st:          st,
		hub:         hub,
		agents:      &AgentRegistry{conns: make(map[string]bool)},
		sess:        NewSessions(),
		gatewayRepo: "api-gateway",
		microVMIP:   "172.18.136.92",
		gatewayVMIP: "172.18.139.186",
	}
}

func (s *Server) Routes() http.Handler {
	mux := http.NewServeMux()

	// auth
	mux.HandleFunc("/api/login", s.handleLogin)
	mux.HandleFunc("/api/logout", s.handleLogout)

	// repos & deploy
	mux.HandleFunc("/api/repos", s.handleListRepos)
	mux.HandleFunc("/api/repos/branches", s.handleListBranches)
	mux.HandleFunc("/api/agents", s.handleListAgents)
	mux.HandleFunc("/api/deployments", s.handleListDeployments)  // GET list
	mux.HandleFunc("/api/deployments/new", s.handleCreateDeployment) // POST
	mux.HandleFunc("/api/deployments/stop", s.handleStopDeployment) // POST

	// environments
	mux.HandleFunc("/api/environments", s.handleEnvironments)        // GET list, POST create
	mux.HandleFunc("/api/environments/", s.handleEnvironmentByID)    // GET/PUT/DELETE /{id}

	// sandboxes
	mux.HandleFunc("/api/sandboxes", s.handleSandboxes)              // GET list, POST create
	mux.HandleFunc("/api/sandboxes/", s.handleSandboxByID)           // GET/PUT/DELETE /{id}
	mux.HandleFunc("/api/sandboxes/clone", s.handleCloneSandbox)      // POST: clone from template

	// templates
	mux.HandleFunc("/api/templates", s.handleTemplates)              // GET list, POST create
	mux.HandleFunc("/api/templates/", s.handleTemplateByID)          // GET/PUT/DELETE /{id}

	// routes
	mux.HandleFunc("/api/routes", s.handleListRoutes)                 // GET: live routes from gateway agent's config.php
	mux.HandleFunc("/api/routes/push", s.handlePushRoutes)            // POST: push routes for a sandbox

	// websocket
	mux.Handle("/ws/agent", s.hub.AgentWS(s.st,
		func(nodeID string) {
			s.agents.mu.Lock()
			s.agents.conns[nodeID] = true
			s.agents.mu.Unlock()
		},
		func(nodeID string) {
			s.agents.mu.Lock()
			delete(s.agents.conns, nodeID)
			s.agents.mu.Unlock()
		},
	))
	mux.HandleFunc("/ws/deployments/", s.hub.DeploymentWS(s.st))

	return s.withAuth(mux)
}

// withAuth checks the session cookie on /api/* (skipping login). /ws/* is
// protected at the handler — we don't pass auth in headers easily on the WS
// upgrade from the browser, so the dashboard sends ?token=... instead.
func (s *Server) withAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.HasPrefix(r.URL.Path, "/api/") || r.URL.Path == "/api/login" {
			next.ServeHTTP(w, r)
			return
		}
		c, err := r.Cookie("sdp_session")
		if err != nil || !s.sess.Valid(c.Value) {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

// userFromContext returns the authenticated user from the session cookie.
// The cookie is set on /api/login; on every other /api/* path we look it
// up here so handlers can record who did what (audit trail).
func (s *Server) userFromContext(r *http.Request) string {
	c, err := r.Cookie("sdp_session")
	if err != nil {
		return ""
	}
	u, _ := s.sess.User(c.Value)
	return u
}

func newID() string {
	b := make([]byte, 8)
	_, _ = rand.Read(b)
	return hex.EncodeToString(b)
}

func writeJSON(w http.ResponseWriter, status int, v interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = encodeJSON(w, v)
}

func writeErr(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, map[string]string{"error": msg})
}

// nowMs is a small helper so tests can stub it later.
func nowMs() int64 { return time.Now().UnixMilli() }
