package handlers

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"Noooste/garage-ui/internal/models"
	"Noooste/garage-ui/internal/services/mocks"

	"github.com/gofiber/fiber/v3"
)

func newClusterTestApp(t *testing.T) (*fiber.App, *mocks.AdminMock) {
	t.Helper()
	admin := &mocks.AdminMock{}
	h := NewClusterHandler(admin)
	app := fiber.New()
	app.Get("/cluster/health", h.GetHealth)
	app.Get("/cluster/status", h.GetStatus)
	app.Get("/cluster/statistics", h.GetStatistics)
	app.Get("/cluster/nodes/:node_id", h.GetNodeInfo)
	app.Get("/cluster/nodes/:node_id/statistics", h.GetNodeStatistics)
	// Extra routes without a node_id param so we can exercise the empty-id gate.
	// Fiber requires a bound param value, so we mount a path with an empty
	// trailing segment explicitly via Locals.
	app.Get("/cluster/nodes-empty", func(c fiber.Ctx) error {
		// Set empty node_id in locals via a route that doesn't capture it.
		return h.GetNodeInfo(c)
	})
	return app, admin
}

func doGet(t *testing.T, app *fiber.App, path string) *http.Response {
	t.Helper()
	req := httptest.NewRequest(http.MethodGet, path, nil)
	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("app.Test %s: %v", path, err)
	}
	return resp
}

func TestCluster_GetHealth_Success(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetClusterHealthFn = func(_ context.Context) (*models.ClusterHealth, error) {
		return &models.ClusterHealth{Status: "healthy"}, nil
	}
	resp := doGet(t, app, "/cluster/health")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.StatusCode)
	}
	var body struct {
		Success bool                 `json:"success"`
		Data    models.ClusterHealth `json:"data"`
	}
	_ = json.NewDecoder(resp.Body).Decode(&body)
	if !body.Success || body.Data.Status != "healthy" {
		t.Errorf("body = %+v", body)
	}
}

func TestCluster_GetHealth_ServiceErrorReturns500(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetClusterHealthFn = func(_ context.Context) (*models.ClusterHealth, error) {
		return nil, errors.New("upstream down")
	}
	resp := doGet(t, app, "/cluster/health")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusInternalServerError {
		t.Fatalf("status = %d, want 500", resp.StatusCode)
	}
}

func TestCluster_GetStatus_Success(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetClusterStatusFn = func(_ context.Context) (*models.ClusterStatus, error) {
		return &models.ClusterStatus{}, nil
	}
	resp := doGet(t, app, "/cluster/status")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d", resp.StatusCode)
	}
}

func TestCluster_GetStatus_ServiceErrorReturns500(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetClusterStatusFn = func(_ context.Context) (*models.ClusterStatus, error) {
		return nil, errors.New("boom")
	}
	resp := doGet(t, app, "/cluster/status")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusInternalServerError {
		t.Fatalf("status = %d, want 500", resp.StatusCode)
	}
}

func TestCluster_GetStatistics_Success(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetClusterStatisticsFn = func(_ context.Context) (*models.ClusterStatistics, error) {
		return &models.ClusterStatistics{}, nil
	}
	resp := doGet(t, app, "/cluster/statistics")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d", resp.StatusCode)
	}
}

func TestCluster_GetStatistics_ServiceErrorReturns500(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetClusterStatisticsFn = func(_ context.Context) (*models.ClusterStatistics, error) {
		return nil, errors.New("boom")
	}
	resp := doGet(t, app, "/cluster/statistics")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusInternalServerError {
		t.Fatalf("status = %d", resp.StatusCode)
	}
}

func TestCluster_GetNodeInfo_Success(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetNodeInfoFn = func(_ context.Context, nodeID string) (*models.MultiNodeResponse, error) {
		if nodeID != "node-1" {
			t.Errorf("nodeID = %q, want node-1", nodeID)
		}
		return &models.MultiNodeResponse{}, nil
	}
	resp := doGet(t, app, "/cluster/nodes/node-1")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d", resp.StatusCode)
	}
}

func TestCluster_GetNodeInfo_ServiceErrorReturns500(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetNodeInfoFn = func(_ context.Context, _ string) (*models.MultiNodeResponse, error) {
		return nil, errors.New("boom")
	}
	resp := doGet(t, app, "/cluster/nodes/n1")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusInternalServerError {
		t.Fatalf("status = %d", resp.StatusCode)
	}
}

func TestCluster_GetNodeStatistics_Success(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetNodeStatisticsFn = func(_ context.Context, nodeID string) (*models.MultiNodeResponse, error) {
		return &models.MultiNodeResponse{}, nil
	}
	resp := doGet(t, app, "/cluster/nodes/n1/statistics")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status = %d", resp.StatusCode)
	}
}

func TestCluster_GetNodeStatistics_ServiceErrorReturns500(t *testing.T) {
	app, admin := newClusterTestApp(t)
	admin.GetNodeStatisticsFn = func(_ context.Context, _ string) (*models.MultiNodeResponse, error) {
		return nil, errors.New("boom")
	}
	resp := doGet(t, app, "/cluster/nodes/n1/statistics")
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusInternalServerError {
		t.Fatalf("status = %d", resp.StatusCode)
	}
}
