package api

import (
	"context"
	"encoding/json"
	"net/http"
	"sort"
	"time"
)

// repoInfo is the shape the dashboard consumes. It carries the nodeID
// the repo lives on, so the dashboard can pick the right agent when
// building a deploy.
type repoInfo struct {
	Name string `json:"name"`
	Node string `json:"node"`
	Path string `json:"path"`
}

// nodeRepoList is the reply shape from the agent's "list_repos" RPC.
type nodeRepoList struct {
	Repos []repoInfo `json:"repos"`
}

// handleListRepos queries every connected agent for its repo list and
// merges the results.
func (s *Server) handleListRepos(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "GET only", http.StatusMethodNotAllowed)
		return
	}
	s.agents.mu.RLock()
	nodes := make([]string, 0, len(s.agents.conns))
	for n, c := range s.agents.conns {
		if c {
			nodes = append(nodes, n)
		}
	}
	s.agents.mu.RUnlock()
	sort.Strings(nodes)

	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()
	merged := make([]repoInfo, 0)
	for _, node := range nodes {
		raw, err := s.hub.CallAgent(ctx, node, "list_repos", map[string]any{}, 0)
		if err != nil {
			continue // agent flaked; skip
		}
		var reply nodeRepoList
		if err := json.Unmarshal(raw, &reply); err != nil {
			continue
		}
		for _, repo := range reply.Repos {
			repo.Node = node
			merged = append(merged, repo)
		}
	}
	// Stable order: by node then name.
	sort.Slice(merged, func(i, j int) bool {
		if merged[i].Node != merged[j].Node {
			return merged[i].Node < merged[j].Node
		}
		return merged[i].Name < merged[j].Name
	})
	writeJSON(w, http.StatusOK, merged)
}

// handleListBranches asks the connected agent that owns `repo` for its
// branches. The repo's node is implied by the prefix path; for now we
// try both connected agents and merge.
func (s *Server) handleListBranches(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "GET only", http.StatusMethodNotAllowed)
		return
	}
	repo := r.URL.Query().Get("repo")
	if repo == "" {
		writeErr(w, http.StatusBadRequest, "repo required")
		return
	}

	s.agents.mu.RLock()
	nodes := make([]string, 0, len(s.agents.conns))
	for n, c := range s.agents.conns {
		if c {
			nodes = append(nodes, n)
		}
	}
	s.agents.mu.RUnlock()

	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()
	branches := make([]string, 0)
	for _, node := range nodes {
		raw, err := s.hub.CallAgent(ctx, node, "list_branches", map[string]any{"repo": repo}, 0)
		if err != nil {
			continue
		}
		var reply struct {
			OK       bool     `json:"ok"`
			Branches []string `json:"branches"`
		}
		if err := json.Unmarshal(raw, &reply); err != nil || !reply.OK {
			continue
		}
		branches = append(branches, reply.Branches...)
	}
	sort.Strings(branches)
	// dedupe
	dedup := make([]string, 0, len(branches))
	seen := map[string]bool{}
	for _, b := range branches {
		if !seen[b] {
			dedup = append(dedup, b)
			seen[b] = true
		}
	}
	writeJSON(w, http.StatusOK, dedup)
}

// handleListAgents returns which agents are currently connected.
func (s *Server) handleListAgents(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "GET only", http.StatusMethodNotAllowed)
		return
	}
	s.agents.mu.RLock()
	defer s.agents.mu.RUnlock()
	out := make([]map[string]string, 0, len(s.agents.conns))
	for n, c := range s.agents.conns {
		if c {
			out = append(out, map[string]string{"node": n})
		}
	}
	sort.Slice(out, func(i, j int) bool { return out[i]["node"] < out[j]["node"] })
	writeJSON(w, http.StatusOK, out)
}

// handleListRoutes proxies the list_routes RPC to the gateway agent.
// The dashboard uses this to show the live <key>_url map from the
// currently-checked-out branch's config.php.
func (s *Server) handleListRoutes(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "GET only", http.StatusMethodNotAllowed)
		return
	}
	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()
	raw, err := s.hub.CallAgent(ctx, "gateway", "list_routes", map[string]any{}, 0)
	if err != nil {
		writeErr(w, http.StatusBadGateway, err.Error())
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(raw)
}
