// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

package handlers

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/MeshCore-Beacon/beacon-server/internal/api"
	"github.com/go-chi/chi/v5"
)

func TestGetStatsObservations_InvalidSince(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/observations", getStatsObservations(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/observations?since=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsPayloadBreakdown_InvalidSince(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/payload-breakdown", getStatsPayloadBreakdown(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/payload-breakdown?since=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopNodes_InvalidLimit(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-nodes", getStatsTopNodes(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-nodes?limit=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopObservers_InvalidSince(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-observers", getStatsTopObservers(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-observers?since=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopObservers_InvalidLimit(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-observers", getStatsTopObservers(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-observers?limit=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsOverview_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/overview", getStatsOverview(stubReader{
		getStatsOverview: func(_ context.Context, _ []string) (*api.StatsOverview, error) {
			return &api.StatsOverview{TotalPackets: 100}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/overview", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsObservations_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/observations", getStatsObservations(stubReader{
		getStatsObservations: func(_ context.Context, _ []string, _ time.Time) ([]api.ObservationPoint, error) {
			return []api.ObservationPoint{{IATA: "YVR", ObservationCount: 10}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/observations", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsPayloadBreakdown_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/payload-breakdown", getStatsPayloadBreakdown(stubReader{
		getStatsPayloadBreakdown: func(_ context.Context, _ []string, _ time.Time) ([]api.PayloadBreakdownItem, error) {
			return []api.PayloadBreakdownItem{{PayloadType: 4, Count: 100}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/payload-breakdown", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsTopNodes_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-nodes", getStatsTopNodes(stubReader{
		getStatsTopNodes: func(_ context.Context, _ []string, _ int32) ([]api.TopNode, error) {
			return []api.TopNode{{IATA: "YVR", ObservationCount: 50}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-nodes", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsTopObservers_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-observers", getStatsTopObservers(stubReader{
		getStatsTopObservers: func(_ context.Context, _ []string, _ time.Time, _ int32) ([]api.TopObserver, error) {
			return []api.TopObserver{{IATA: "YVR", ObservationCount: 20}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-observers", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsTopAdvertisers_InvalidSince(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-advertisers", getStatsTopAdvertisers(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-advertisers?since=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopAdvertisers_InvalidLimit(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-advertisers", getStatsTopAdvertisers(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-advertisers?limit=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopAdvertisers_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-advertisers", getStatsTopAdvertisers(stubReader{
		getStatsTopAdvertisers: func(_ context.Context, _ []string, _ time.Time, _ int32) ([]api.TopAdvertiser, error) {
			return []api.TopAdvertiser{{IATA: "YVR", AdvertCount: 5}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-advertisers", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsClockDrift_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/clock-drift", getStatsClockDrift(stubReader{
		getStatsClockDrift: func(_ context.Context, _ []string, _ int32) ([]api.ClockDriftEntry, error) {
			return []api.ClockDriftEntry{{ClockDriftSeconds: -600}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/clock-drift", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsClockDrift_MultiIATA_PassedThrough(t *testing.T) {
	var gotIATAs []string
	r := chi.NewRouter()
	r.Get("/stats/clock-drift", getStatsClockDrift(stubReader{
		getStatsClockDrift: func(_ context.Context, iatas []string, _ int32) ([]api.ClockDriftEntry, error) {
			gotIATAs = iatas
			return nil, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/clock-drift?iatas=YVR,YYJ", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
	if len(gotIATAs) != 2 || gotIATAs[0] != "YVR" || gotIATAs[1] != "YYJ" {
		t.Errorf("expected [YVR YYJ], got %v", gotIATAs)
	}
}

func TestGetStatsClockDrift_InvalidLimit(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/clock-drift", getStatsClockDrift(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/clock-drift?limit=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopTalkers_InvalidSince(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-talkers", getStatsTopTalkers(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-talkers?since=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopTalkers_InvalidLimit(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-talkers", getStatsTopTalkers(stubReader{}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-talkers?limit=notanint", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestGetStatsTopTalkers_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/top-talkers", getStatsTopTalkers(stubReader{
		getStatsTopTalkers: func(_ context.Context, _ []string, _ time.Time, _ int32) ([]api.TopTalker, error) {
			return []api.TopTalker{{SenderName: "Robbie", MessageCount: 3}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/top-talkers", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsRadioPresets_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/radio-presets", getStatsRadioPresets(stubReader{
		getRadioPresets: func(_ context.Context, _ string, _ []string) ([]api.RadioPreset, error) {
			return []api.RadioPreset{{Preset: "915.0,125,7", IATA: "YVR"}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/radio-presets", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsScopes_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/scopes", getStatsScopes(stubReader{
		getScopeStats: func(_ context.Context) ([]api.ScopeStats, error) {
			return []api.ScopeStats{{Name: "#bc", PacketCount: 100}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/scopes", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}

func TestGetStatsNodeTypes_OK(t *testing.T) {
	r := chi.NewRouter()
	r.Get("/stats/node-types", getStatsNodeTypes(stubReader{
		getStatsNodeTypes: func(_ context.Context, _ []string) ([]api.NodeTypeCount, error) {
			return []api.NodeTypeCount{{NodeType: 1, Count: 5}}, nil
		},
	}))
	req := httptest.NewRequest(http.MethodGet, "/stats/node-types", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}
}
