package api

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

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/jackc/pgx/v5/pgxpool"

	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)

// newHistoryRouter mounts ONLY /api/me/history, injecting the user directly
// into context (bypassing RequireUser), mirroring the pattern used by peer
// tests such as library_albums_test.go and quarantine_test.go.
func newHistoryRouter(h *handlers) chi.Router {
	r := chi.NewRouter()
	r.Get("/api/me/history", h.handleGetMyHistory)
	return r
}

func doGetHistory(h *handlers, _ *pgxpool.Pool, user dbq.User, query string) *httptest.ResponseRecorder {
	url := "/api/me/history"
	if query != "" {
		url += "?" + query
	}
	req := httptest.NewRequest(http.MethodGet, url, nil)
	req = withUser(req, user)
	w := httptest.NewRecorder()
	newHistoryRouter(h).ServeHTTP(w, req)
	return w
}

func seedPlayEvent(t *testing.T, pool *pgxpool.Pool, userID, trackID, sessionID pgtype.UUID, startedAt time.Time, skipped bool) {
	t.Helper()
	_, err := pool.Exec(context.Background(),
		`INSERT INTO play_events (user_id, track_id, session_id, started_at, was_skipped)
		 VALUES ($1, $2, $3, $4, $5)`,
		userID, trackID, sessionID, startedAt, skipped,
	)
	if err != nil {
		t.Fatalf("seedPlayEvent: %v", err)
	}
}

func seedPlaySession(t *testing.T, pool *pgxpool.Pool, userID pgtype.UUID, startedAt time.Time) pgtype.UUID {
	t.Helper()
	var id pgtype.UUID
	err := pool.QueryRow(context.Background(),
		`INSERT INTO play_sessions (user_id, started_at, last_event_at)
		 VALUES ($1, $2, $2) RETURNING id`,
		userID, startedAt,
	).Scan(&id)
	if err != nil {
		t.Fatalf("seedPlaySession: %v", err)
	}
	return id
}

func quarantineTrack(t *testing.T, pool *pgxpool.Pool, userID, trackID pgtype.UUID) {
	t.Helper()
	_, err := pool.Exec(context.Background(),
		`INSERT INTO lidarr_quarantine (user_id, track_id, reason) VALUES ($1, $2, 'bad_rip')`,
		userID, trackID,
	)
	if err != nil {
		t.Fatalf("quarantineTrack: %v", err)
	}
}

func TestHistory_FilteringAndPagination(t *testing.T) {
	h, pool := testHandlers(t)
	truncateLibrary(t, pool)

	userA := seedUser(t, pool, "alice", "pw", false)
	userB := seedUser(t, pool, "bob", "pw", false)

	artist := seedArtist(t, pool, "Artist 1")
	album := seedAlbum(t, pool, artist.ID, "Album 1", 2024)
	t1 := seedTrack(t, pool, album.ID, artist.ID, "Track 1", 1, 180000)
	t2 := seedTrack(t, pool, album.ID, artist.ID, "Track 2", 2, 180000)
	t3 := seedTrack(t, pool, album.ID, artist.ID, "Track 3", 3, 180000)
	t4 := seedTrack(t, pool, album.ID, artist.ID, "Track 4", 4, 180000)
	tSkipped := seedTrack(t, pool, album.ID, artist.ID, "Skipped Track", 5, 60000)
	tQuar := seedTrack(t, pool, album.ID, artist.ID, "Quarantined Track", 6, 120000)
	tB := seedTrack(t, pool, album.ID, artist.ID, "Bob Only Track", 7, 200000)

	quarantineTrack(t, pool, userA.ID, tQuar.ID)

	now := time.Now().UTC()
	sessA := seedPlaySession(t, pool, userA.ID, now.Add(-5*time.Hour))
	sessB := seedPlaySession(t, pool, userB.ID, now.Add(-1*time.Hour))

	seedPlayEvent(t, pool, userA.ID, t1.ID, sessA, now.Add(-4*time.Hour), false)
	seedPlayEvent(t, pool, userA.ID, t2.ID, sessA, now.Add(-3*time.Hour), false)
	seedPlayEvent(t, pool, userA.ID, t3.ID, sessA, now.Add(-2*time.Hour), false)
	seedPlayEvent(t, pool, userA.ID, t4.ID, sessA, now.Add(-1*time.Hour), false)
	seedPlayEvent(t, pool, userA.ID, tSkipped.ID, sessA, now.Add(-30*time.Minute), true)
	seedPlayEvent(t, pool, userA.ID, tQuar.ID, sessA, now.Add(-15*time.Minute), false)
	seedPlayEvent(t, pool, userB.ID, tB.ID, sessB, now.Add(-10*time.Minute), false)

	t.Run("filters skipped + quarantined, returns DESC", func(t *testing.T) {
		w := doGetHistory(h, pool, userA, "")
		if w.Code != http.StatusOK {
			t.Fatalf("status: got %d, want 200; body=%s", w.Code, w.Body.String())
		}
		var resp HistoryResponse
		if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
			t.Fatalf("decode: %v", err)
		}
		if len(resp.Events) != 4 {
			t.Fatalf("len: got %d, want 4 (skipped + quarantined excluded); body=%s",
				len(resp.Events), w.Body.String())
		}
		// Newest first: t4, t3, t2, t1.
		want := []pgtype.UUID{t4.ID, t3.ID, t2.ID, t1.ID}
		for i, e := range resp.Events {
			if e.Track.ID != uuidToString(want[i]) {
				t.Errorf("event[%d].track.id: got %s, want %s",
					i, e.Track.ID, uuidToString(want[i]))
			}
		}
		if resp.HasMore {
			t.Errorf("has_more: got true, want false (4 events < limit 50)")
		}
	})

	t.Run("pagination via offset/limit", func(t *testing.T) {
		w := doGetHistory(h, pool, userA, "offset=2&limit=2")
		if w.Code != http.StatusOK {
			t.Fatalf("status: got %d", w.Code)
		}
		var resp HistoryResponse
		_ = json.NewDecoder(w.Body).Decode(&resp)
		if len(resp.Events) != 2 {
			t.Fatalf("len: got %d, want 2", len(resp.Events))
		}
		if resp.Events[0].Track.ID != uuidToString(t2.ID) || resp.Events[1].Track.ID != uuidToString(t1.ID) {
			t.Errorf("page-2 ids: got %s,%s; want %s,%s",
				resp.Events[0].Track.ID, resp.Events[1].Track.ID,
				uuidToString(t2.ID), uuidToString(t1.ID))
		}
	})

	t.Run("has_more=true when len(rows) == limit", func(t *testing.T) {
		w := doGetHistory(h, pool, userA, "limit=1")
		var resp HistoryResponse
		_ = json.NewDecoder(w.Body).Decode(&resp)
		if !resp.HasMore {
			t.Errorf("has_more: got false, want true (4 events, limit 1)")
		}
	})

	t.Run("user B sees only their own play", func(t *testing.T) {
		w := doGetHistory(h, pool, userB, "")
		var resp HistoryResponse
		_ = json.NewDecoder(w.Body).Decode(&resp)
		if len(resp.Events) != 1 {
			t.Fatalf("len: got %d, want 1", len(resp.Events))
		}
		if resp.Events[0].Track.ID != uuidToString(tB.ID) {
			t.Errorf("user B's event: got track %s, want %s",
				resp.Events[0].Track.ID, uuidToString(tB.ID))
		}
	})

	t.Run("unauthenticated returns 401", func(t *testing.T) {
		// No withUser wrapper — exercises the handler's own auth guard.
		req := httptest.NewRequest(http.MethodGet, "/api/me/history", nil)
		w := httptest.NewRecorder()
		newHistoryRouter(h).ServeHTTP(w, req)
		if w.Code != http.StatusUnauthorized {
			t.Errorf("unauth: got %d, want 401", w.Code)
		}
	})
}
