package main

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

	"github.com/kami/maven/internal/ipc"
)

// eventsCore serves a canned intake journal. Embedding
// ipc.UnimplementedCoreAPI means any other call fails loudly.
type eventsCore struct {
	ipc.UnimplementedCoreAPI
	events []ipc.IntakeEvent
	err    error
	gotN   int
}

func (c *eventsCore) RecentEvents(_ context.Context, n int) ([]ipc.IntakeEvent, error) {
	c.gotN = n
	return c.events, c.err
}

func getEvents(t *testing.T, core ipc.CoreAPI) *httptest.ResponseRecorder {
	t.Helper()
	w := httptest.NewRecorder()
	handleEvents(w, httptest.NewRequest(http.MethodGet, "/events", nil), core)
	return w
}

func TestEventsPageRendersTheJournal(t *testing.T) {
	core := &eventsCore{events: []ipc.IntakeEvent{
		{Source: "rss:tech", Kind: "note", Title: "Вышло ядро 6.19", Priority: "low",
			OccurredAt: time.Date(2026, 8, 1, 7, 15, 0, 0, time.UTC)},
		{Source: "ambient:notif", Kind: "fact", Title: "calendar_event_20260801_планёрка",
			Body: "10:00-11:00 планёрка", Priority: "low",
			OccurredAt: time.Date(2026, 8, 1, 10, 0, 0, 0, time.UTC)},
	}}
	w := getEvents(t, core)
	if w.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200", w.Code)
	}
	body := w.Body.String()
	for _, want := range []string{"rss:tech", "Вышло ядро 6.19", "ambient:notif", "10:00-11:00 планёрка", "01.08 10:00:00"} {
		if !strings.Contains(body, want) {
			t.Errorf("page does not mention %q", want)
		}
	}
	if core.gotN != eventsPageLimit {
		t.Errorf("asked core for %d events, want %d", core.gotN, eventsPageLimit)
	}
}

func TestEventsPageSaysNothingArrived(t *testing.T) {
	w := getEvents(t, &eventsCore{})
	if w.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200", w.Code)
	}
	if !strings.Contains(w.Body.String(), "nothing has arrived yet") {
		t.Error("empty journal did not render the empty-state line")
	}
}

func TestEventsPageReportsAReadFailure(t *testing.T) {
	// An unreachable journal must say so rather than render an empty table,
	// which would imply nothing arrived.
	w := getEvents(t, &eventsCore{err: errors.New("core is down")})
	if w.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200 with the error rendered", w.Code)
	}
	body := w.Body.String()
	if !strings.Contains(body, "journal unavailable") || !strings.Contains(body, "core is down") {
		t.Errorf("page did not report the read failure: %s", body)
	}
	if strings.Contains(body, "nothing has arrived yet") {
		t.Error("a failed read rendered as an empty journal")
	}
}

func TestEventsPageWithoutCore(t *testing.T) {
	w := getEvents(t, nil)
	if w.Code != http.StatusServiceUnavailable {
		t.Errorf("status = %d, want 503", w.Code)
	}
}

func TestEventsPageEscapesIntakeText(t *testing.T) {
	// Titles come from outside — a feed headline, a notification. They are shown
	// on a page and must never be able to inject markup into it.
	core := &eventsCore{events: []ipc.IntakeEvent{{
		Source: "rss:x", Kind: "note", Priority: "low",
		Title:      `<script>alert(1)</script>`,
		OccurredAt: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC),
	}}}
	body := getEvents(t, core).Body.String()
	if strings.Contains(body, "<script>alert(1)</script>") {
		t.Error("intake title was not escaped")
	}
	if !strings.Contains(body, "&lt;script&gt;") {
		t.Error("intake title is missing from the page entirely")
	}
}
