package cluster

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

const (
	rqliteBaseURL = "http://localhost:5001"
	httpTimeout   = 10 * time.Second
)

// rqliteStatus represents the relevant fields from the RQLite /status endpoint.
type rqliteStatus struct {
	Store struct {
		Raft struct {
			State        string `json:"state"`
			AppliedIndex uint64 `json:"applied_index"`
			CommitIndex  uint64 `json:"commit_index"`
			Term         uint64 `json:"current_term"`
			Leader       string `json:"leader"`
		} `json:"raft"`
		Dir     string `json:"dir"`
		NodeID  string `json:"node_id"`
		Address string `json:"addr"`
	} `json:"store"`
	HTTP struct {
		Address string `json:"addr"`
	} `json:"http"`
	Node struct {
		Uptime string `json:"uptime"`
	} `json:"node"`
}

// rqliteNode represents a node from the /nodes endpoint.
type rqliteNode struct {
	ID      string `json:"id"`
	Address string `json:"addr"`
	Leader  bool   `json:"leader"`
	Voter   bool   `json:"voter"`
	Reachable bool `json:"reachable"`
	Time    float64 `json:"time"`
	TimeS   string  `json:"time_s"`
}

// HandleStatus handles the "orama cluster status" command.
func HandleStatus(args []string) {
	if hasFlag(args, "--all") {
		fmt.Printf("Remote node aggregation via SSH is not yet implemented.\n")
		fmt.Printf("Currently showing local node status only.\n\n")
	}

	fmt.Printf("Cluster Status\n")
	fmt.Printf("==============\n\n")

	// Query RQLite status
	status, err := queryRQLiteStatus()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error querying RQLite status: %v\n", err)
		fmt.Printf("RQLite may not be running on this node.\n\n")
	} else {
		printLocalStatus(status)
	}

	// Query RQLite nodes
	nodes, err := queryRQLiteNodes(true)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error querying RQLite nodes: %v\n", err)
	} else {
		printNodesTable(nodes)
	}

	// Query Olric status (best-effort)
	printOlricStatus()
}

// queryRQLiteStatus queries the local RQLite /status endpoint.
func queryRQLiteStatus() (*rqliteStatus, error) {
	client := &http.Client{Timeout: httpTimeout}
	resp, err := client.Get(rqliteBaseURL + "/status")
	if err != nil {
		return nil, fmt.Errorf("connect to RQLite: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read response: %w", err)
	}

	var status rqliteStatus
	if err := json.Unmarshal(body, &status); err != nil {
		return nil, fmt.Errorf("parse response: %w", err)
	}

	return &status, nil
}

// queryRQLiteNodes queries the local RQLite /nodes endpoint.
// If includeNonVoters is true, appends ?nonvoters to the query.
func queryRQLiteNodes(includeNonVoters bool) (map[string]*rqliteNode, error) {
	client := &http.Client{Timeout: httpTimeout}

	url := rqliteBaseURL + "/nodes"
	if includeNonVoters {
		url += "?nonvoters"
	}

	resp, err := client.Get(url)
	if err != nil {
		return nil, fmt.Errorf("connect to RQLite: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read response: %w", err)
	}

	var nodes map[string]*rqliteNode
	if err := json.Unmarshal(body, &nodes); err != nil {
		return nil, fmt.Errorf("parse response: %w", err)
	}

	return nodes, nil
}

// printLocalStatus prints the local node's RQLite status.
func printLocalStatus(s *rqliteStatus) {
	fmt.Printf("Local Node\n")
	fmt.Printf("  Node ID:       %s\n", s.Store.NodeID)
	fmt.Printf("  Raft Address:  %s\n", s.Store.Address)
	fmt.Printf("  HTTP Address:  %s\n", s.HTTP.Address)
	fmt.Printf("  Raft State:    %s\n", strings.ToUpper(s.Store.Raft.State))
	fmt.Printf("  Raft Term:     %d\n", s.Store.Raft.Term)
	fmt.Printf("  Applied Index: %d\n", s.Store.Raft.AppliedIndex)
	fmt.Printf("  Commit Index:  %d\n", s.Store.Raft.CommitIndex)
	fmt.Printf("  Leader:        %s\n", s.Store.Raft.Leader)
	if s.Node.Uptime != "" {
		fmt.Printf("  Uptime:        %s\n", s.Node.Uptime)
	}
	fmt.Println()
}

// printNodesTable prints a formatted table of all cluster nodes.
func printNodesTable(nodes map[string]*rqliteNode) {
	if len(nodes) == 0 {
		fmt.Printf("No nodes found in cluster.\n\n")
		return
	}

	fmt.Printf("Cluster Nodes (%d total)\n", len(nodes))
	fmt.Printf("%-20s %-30s %-8s %-10s %-10s %-12s\n",
		"NODE ID", "ADDRESS", "VOTER", "LEADER", "REACHABLE", "LATENCY")
	fmt.Printf("%-20s %-30s %-8s %-10s %-10s %-12s\n",
		strings.Repeat("-", 20),
		strings.Repeat("-", 30),
		strings.Repeat("-", 8),
		strings.Repeat("-", 10),
		strings.Repeat("-", 10),
		strings.Repeat("-", 12))

	for id, node := range nodes {
		nodeID := id
		if len(nodeID) > 20 {
			nodeID = nodeID[:17] + "..."
		}

		voter := "no"
		if node.Voter {
			voter = "yes"
		}

		leader := "no"
		if node.Leader {
			leader = "yes"
		}

		reachable := "no"
		if node.Reachable {
			reachable = "yes"
		}

		latency := "-"
		if node.TimeS != "" {
			latency = node.TimeS
		} else if node.Time > 0 {
			latency = fmt.Sprintf("%.3fs", node.Time)
		}

		fmt.Printf("%-20s %-30s %-8s %-10s %-10s %-12s\n",
			nodeID, node.Address, voter, leader, reachable, latency)
	}
	fmt.Println()
}

// printOlricStatus attempts to query the local Olric status endpoint.
func printOlricStatus() {
	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get("http://localhost:3320/")
	if err != nil {
		fmt.Printf("Olric: not reachable on localhost:3320 (%v)\n\n", err)
		return
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("Olric: reachable but could not read response\n\n")
		return
	}

	if resp.StatusCode == http.StatusOK {
		fmt.Printf("Olric: reachable (HTTP %d)\n", resp.StatusCode)
		// Try to parse as JSON for a nicer display
		var data map[string]interface{}
		if err := json.Unmarshal(body, &data); err == nil {
			for key, val := range data {
				fmt.Printf("  %s: %v\n", key, val)
			}
		} else {
			// Not JSON, print raw (truncated)
			raw := strings.TrimSpace(string(body))
			if len(raw) > 200 {
				raw = raw[:200] + "..."
			}
			if raw != "" {
				fmt.Printf("  Response: %s\n", raw)
			}
		}
	} else {
		fmt.Printf("Olric: reachable but returned HTTP %d\n", resp.StatusCode)
	}
	fmt.Println()
}
