package server

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

	"galaxy/backend/internal/geo"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap/zaptest"
)

type fakeAdminGeoLister struct {
	entries []geo.CountryCounter
	err     error
}

func (f *fakeAdminGeoLister) ListUserCounters(_ context.Context, _ uuid.UUID) ([]geo.CountryCounter, error) {
	if f.err != nil {
		return nil, f.err
	}
	return f.entries, nil
}

func newAdminGeoEngine(t *testing.T, fake AdminGeoLister) *gin.Engine {
	t.Helper()
	gin.SetMode(gin.TestMode)
	r := gin.New()
	h := NewAdminGeoHandlers(fake, zaptest.NewLogger(t))
	r.GET("/api/v1/admin/geo/users/:user_id/countries", h.ListUserCountries())
	return r
}

func TestAdminGeoListUserCountriesSuccess(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC)
	fake := &fakeAdminGeoLister{entries: []geo.CountryCounter{
		{Country: "AU", Count: 3, LastSeenAt: &now},
		{Country: "DE", Count: 1, LastSeenAt: nil},
	}}
	r := newAdminGeoEngine(t, fake)

	userID := uuid.New()
	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+userID.String()+"/countries", nil)
	rec := httptest.NewRecorder()

	r.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status: want 200, got %d (%s)", rec.Code, rec.Body.String())
	}
	var body adminGeoListWire
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if body.UserID != userID.String() {
		t.Errorf("user_id: want %s, got %s", userID, body.UserID)
	}
	if len(body.Items) != 2 {
		t.Fatalf("items: want 2, got %d (%+v)", len(body.Items), body.Items)
	}
	if body.Items[0].Country != "AU" || body.Items[0].Count != 3 || body.Items[0].LastSeenAt == nil {
		t.Errorf("items[0] mismatch: %+v", body.Items[0])
	}
	if body.Items[1].Country != "DE" || body.Items[1].Count != 1 || body.Items[1].LastSeenAt != nil {
		t.Errorf("items[1] mismatch: %+v", body.Items[1])
	}
}

func TestAdminGeoListUserCountriesEmpty(t *testing.T) {
	t.Parallel()

	r := newAdminGeoEngine(t, &fakeAdminGeoLister{})

	userID := uuid.New()
	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+userID.String()+"/countries", nil)
	rec := httptest.NewRecorder()

	r.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status: want 200, got %d", rec.Code)
	}
	var body adminGeoListWire
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if body.Items == nil {
		t.Fatal("items: want non-nil empty slice, got nil")
	}
	if len(body.Items) != 0 {
		t.Fatalf("items: want empty, got %+v", body.Items)
	}
}

func TestAdminGeoListUserCountriesInvalidUserID(t *testing.T) {
	t.Parallel()

	r := newAdminGeoEngine(t, &fakeAdminGeoLister{})

	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/not-a-uuid/countries", nil)
	rec := httptest.NewRecorder()

	r.ServeHTTP(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status: want 400, got %d", rec.Code)
	}
}

func TestAdminGeoListUserCountriesStoreError(t *testing.T) {
	t.Parallel()

	r := newAdminGeoEngine(t, &fakeAdminGeoLister{err: errors.New("boom")})

	userID := uuid.New()
	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+userID.String()+"/countries", nil)
	rec := httptest.NewRecorder()

	r.ServeHTTP(rec, req)

	if rec.Code != http.StatusInternalServerError {
		t.Fatalf("status: want 500, got %d", rec.Code)
	}
}

func TestAdminGeoListUserCountriesNilServiceReturns501(t *testing.T) {
	t.Parallel()

	gin.SetMode(gin.TestMode)
	r := gin.New()
	h := NewAdminGeoHandlers(nil, zaptest.NewLogger(t))
	r.GET("/api/v1/admin/geo/users/:user_id/countries", h.ListUserCountries())

	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+uuid.New().String()+"/countries", nil)
	rec := httptest.NewRecorder()

	r.ServeHTTP(rec, req)

	if rec.Code != http.StatusNotImplemented {
		t.Fatalf("status: want 501, got %d", rec.Code)
	}
}
