package api

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

	"crypto-miner-server/internal/db"
	"crypto-miner-server/internal/models"
	"github.com/go-chi/chi/v5"
)

func newTestHandler(t *testing.T) *Handler {
	t.Helper()
	database, err := db.New(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = database.Close() })
	return NewHandler(database)
}

func TestHealthCheckReturnsOK(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil)
	rec := httptest.NewRecorder()
	h.HealthCheck(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
	var body map[string]string
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body["status"] != "ok" {
		t.Fatalf("unexpected body: %v", body)
	}
}

func TestListAgentsEmptyArray(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/agents", nil)
	rec := httptest.NewRecorder()
	h.ListAgents(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
	}
	if rec.Body.String() != "[]\n" && rec.Body.String() != "[]" {
		var agents []json.RawMessage
		if err := json.Unmarshal(rec.Body.Bytes(), &agents); err != nil {
			t.Fatal(err)
		}
		if len(agents) != 0 {
			t.Fatalf("expected empty list, got %d", len(agents))
		}
	}
}

func TestGetAgentStatsLimitCap(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/agents/missing-agent/stats?limit=5000", nil)
	rec := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Get("/agents/{id}/stats", h.GetAgentStats)
	r.ServeHTTP(rec, req)
	if rec.Code == http.StatusInternalServerError {
		t.Fatalf("limit cap caused 500: %s", rec.Body.String())
	}
}

func TestGetRecentSharesLimitCap(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/shares?limit=999999", nil)
	rec := httptest.NewRecorder()
	h.GetRecentShares(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
	}
}

func TestGetAgentNotFound(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/agents/nope", nil)
	rec := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Get("/agents/{id}", h.GetAgent)
	r.ServeHTTP(rec, req)
	if rec.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", rec.Code)
	}
}

const (
	handlerAgentStatsDefaultLimit = 100
	handlerAgentStatsMaxLimit     = 1000
	handlerSharesDefaultLimit     = 50
	handlerSharesMaxLimit         = 1000
	handlerListBuildsLimit        = 50
)

func TestHandlerConstants(t *testing.T) {
	if handlerAgentStatsDefaultLimit != 100 || handlerAgentStatsMaxLimit != 1000 {
		t.Fatal("agent stats limit constants drifted")
	}
	if handlerSharesDefaultLimit != 50 || handlerSharesMaxLimit != 1000 {
		t.Fatal("shares limit constants drifted")
	}
	if handlerListBuildsLimit != 50 {
		t.Fatal("list builds limit constant drifted")
	}
}

func TestGetDashboardStatsEmptyFleet(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/dashboard/stats", nil)
	rec := httptest.NewRecorder()
	h.GetDashboardStats(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
	}
}

func TestListBuildsEmptyArray(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/builds", nil)
	rec := httptest.NewRecorder()
	h.ListBuilds(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
	var builds []json.RawMessage
	if err := json.Unmarshal(rec.Body.Bytes(), &builds); err != nil {
		t.Fatal(err)
	}
	if len(builds) != 0 {
		t.Fatalf("expected empty builds, got %d", len(builds))
	}
}

func TestPinBuildAndUnpinAll(t *testing.T) {
	h := newTestHandler(t)
	database := h.db
	if err := database.InsertBuild(&models.BuildRecord{
		ID: "pin-me", WorkerName: "w", ServerURL: "http://x", Wallet: "48x",
		FilePath: "/tmp/x", FileName: "x.exe", Platform: "windows",
	}); err != nil {
		t.Fatal(err)
	}

	r := chi.NewRouter()
	r.Put("/builds/{id}/pin", h.PinBuild)
	r.Delete("/builds/pin", h.UnpinAll)

	req := httptest.NewRequest(http.MethodPut, "/builds/pin-me/pin", nil)
	rec := httptest.NewRecorder()
	r.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("pin status %d body %s", rec.Code, rec.Body.String())
	}

	req = httptest.NewRequest(http.MethodDelete, "/builds/pin", nil)
	rec = httptest.NewRecorder()
	r.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("unpin status %d body %s", rec.Code, rec.Body.String())
	}
}

func TestDeleteBuildSuccess(t *testing.T) {
	h := newTestHandler(t)
	if err := h.db.InsertBuild(&models.BuildRecord{
		ID: "del-me", WorkerName: "w", ServerURL: "http://x", Wallet: "48x",
		FilePath: "/tmp/x", FileName: "x.exe", Platform: "windows",
	}); err != nil {
		t.Fatal(err)
	}

	r := chi.NewRouter()
	r.Delete("/builds/{id}", h.DeleteBuild)
	req := httptest.NewRequest(http.MethodDelete, "/builds/del-me", nil)
	rec := httptest.NewRecorder()
	r.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("delete status %d body %s", rec.Code, rec.Body.String())
	}
}

func TestGetRecentSharesDefaultLimit(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/shares", nil)
	rec := httptest.NewRecorder()
	h.GetRecentShares(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
}

func TestGetAgentStatsInvalidLimitUsesDefault(t *testing.T) {
	h := newTestHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/agents/x/stats?limit=abc", nil)
	rec := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Get("/agents/{id}/stats", h.GetAgentStats)
	r.ServeHTTP(rec, req)
	if rec.Code == http.StatusInternalServerError {
		t.Fatalf("invalid limit should not 500: %s", rec.Body.String())
	}
}
