package handlers

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gin-gonic/gin"

	"armature/metrics"
	"armature/store"
)

// mockHub implements metrics.ConnCounter for tests.
type mockHub struct{ count int }

func (m *mockHub) ConnCount() int { return m.count }

// mockBus implements metrics.BusCounter for tests.
type mockBus struct{ pub, del int64 }

func (m *mockBus) PublishCount() int64 { return m.pub }
func (m *mockBus) DeliverCount() int64 { return m.del }

func TestMetrics_SQLiteShape(t *testing.T) {
	gin.SetMode(gin.TestMode)

	collector := metrics.NewCollector(
		"test-node",
		nil, // no DB
		&mockHub{count: 5},
		&mockBus{pub: 10, del: 20},
		store.Stores{}, // no cluster store
		func() (uint64, uint64, float64) { return 42, 3, 12.5 },
		func() int64 { return 7 },
		time.Now().Add(-10*time.Minute),
	)

	h := NewMetricsHandler(collector)
	r := gin.New()
	r.GET("/api/v1/admin/metrics", h.GetMetrics)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/metrics", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != 200 {
		t.Fatalf("status = %d, want 200", w.Code)
	}

	var snap metrics.Snapshot
	if err := json.Unmarshal(w.Body.Bytes(), &snap); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	// Node ID
	if snap.NodeID != "test-node" {
		t.Errorf("node_id = %q, want %q", snap.NodeID, "test-node")
	}

	// Runtime
	if snap.Runtime.WSClients != 5 {
		t.Errorf("ws_clients = %d, want 5", snap.Runtime.WSClients)
	}
	if snap.Runtime.UptimeSec < 600 {
		t.Errorf("uptime_sec = %f, want >= 600", snap.Runtime.UptimeSec)
	}

	// Cluster should be nil (SQLite)
	if snap.Cluster != nil {
		t.Errorf("cluster should be nil for SQLite, got %+v", snap.Cluster)
	}

	// Extensions
	if snap.Extensions.StarlarkExecTotal != 42 {
		t.Errorf("starlark_exec_total = %d, want 42", snap.Extensions.StarlarkExecTotal)
	}
	if snap.Extensions.StarlarkErrorsTotal != 3 {
		t.Errorf("starlark_errors_total = %d, want 3", snap.Extensions.StarlarkErrorsTotal)
	}
	if snap.Extensions.StarlarkAvgDuration != 12.5 {
		t.Errorf("starlark_avg_duration_ms = %f, want 12.5", snap.Extensions.StarlarkAvgDuration)
	}
	if snap.Extensions.TriggerFiresTotal != 7 {
		t.Errorf("trigger_fires_total = %d, want 7", snap.Extensions.TriggerFiresTotal)
	}
	if snap.Extensions.EventBusPublished != 10 {
		t.Errorf("event_bus_published = %d, want 10", snap.Extensions.EventBusPublished)
	}
	if snap.Extensions.EventBusDelivered != 20 {
		t.Errorf("event_bus_delivered = %d, want 20", snap.Extensions.EventBusDelivered)
	}
}

func TestMetrics_WithCluster(t *testing.T) {
	gin.SetMode(gin.TestMode)

	mock := &mockClusterStore{
		nodes: []store.ClusterNode{
			{
				NodeID:    "node-1",
				Endpoint:  "http://node-1:8080",
				Heartbeat: time.Now(),
				Stats:     json.RawMessage(`{"uptime_sec":120,"ws_clients":3}`),
			},
		},
	}

	collector := metrics.NewCollector(
		"test-node",
		nil,
		&mockHub{count: 3},
		&mockBus{},
		store.Stores{Cluster: mock},
		func() (uint64, uint64, float64) { return 0, 0, 0 },
		func() int64 { return 0 },
		time.Now(),
	)

	h := NewMetricsHandler(collector)
	r := gin.New()
	r.GET("/api/v1/admin/metrics", h.GetMetrics)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/metrics", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != 200 {
		t.Fatalf("status = %d, want 200", w.Code)
	}

	var snap metrics.Snapshot
	if err := json.Unmarshal(w.Body.Bytes(), &snap); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if snap.Cluster == nil {
		t.Fatal("cluster should not be nil with cluster store")
	}
	if snap.Cluster.Size != 1 {
		t.Errorf("cluster.size = %d, want 1", snap.Cluster.Size)
	}
	if snap.Cluster.Nodes[0].NodeID != "node-1" {
		t.Errorf("node_id = %q, want %q", snap.Cluster.Nodes[0].NodeID, "node-1")
	}
	if snap.Cluster.Nodes[0].UptimeSec != 120 {
		t.Errorf("uptime_sec = %f, want 120", snap.Cluster.Nodes[0].UptimeSec)
	}
}

func TestMetrics_ExtensionCounters(t *testing.T) {
	gin.SetMode(gin.TestMode)

	collector := metrics.NewCollector(
		"test-node",
		nil,
		&mockHub{},
		&mockBus{pub: 100, del: 500},
		store.Stores{},
		func() (uint64, uint64, float64) { return 1000, 50, 8.3 },
		func() int64 { return 25 },
		time.Now(),
	)

	h := NewMetricsHandler(collector)
	r := gin.New()
	r.GET("/api/v1/admin/metrics", h.GetMetrics)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/metrics", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var snap metrics.Snapshot
	_ = json.Unmarshal(w.Body.Bytes(), &snap)

	if snap.Extensions.StarlarkExecTotal != 1000 {
		t.Errorf("exec total = %d, want 1000", snap.Extensions.StarlarkExecTotal)
	}
	if snap.Extensions.EventBusPublished != 100 {
		t.Errorf("bus published = %d, want 100", snap.Extensions.EventBusPublished)
	}
	if snap.Extensions.EventBusDelivered != 500 {
		t.Errorf("bus delivered = %d, want 500", snap.Extensions.EventBusDelivered)
	}
}
