package services

import (
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
)

// t-paliad-177 Slice 2 — pins FormatTimelineICS behavior.
//
// Trust contract: lawyers subscribe the .ics URL in Outlook / Apple
// Calendar; predicted dates must NOT appear (faraday-Q6 / m's pick),
// and re-export must update (not duplicate) prior entries.

func TestFormatTimelineICS_OnlyDeadlinesAndAppointments(t *testing.T) {
	due := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
	start := time.Date(2026, 7, 1, 9, 30, 0, 0, time.UTC)
	dID := uuid.New()
	aID := uuid.New()

	events := []TimelineEvent{
		{Kind: "deadline", Status: "open", Date: &due, Title: "Klageerwiderung", DeadlineID: &dID},
		{Kind: "appointment", Status: "open", Date: &start, Title: "Hearing", AppointmentID: &aID},
		{Kind: "milestone", Status: "done", Date: &due, Title: "Filed"},
		{Kind: "projected", Status: "predicted", Date: &due, Title: "Predicted R.29c"},
		{Kind: "projected", Status: "court_set", Date: &start, Title: "Court set HV"},
	}
	out := FormatTimelineICS(events, "Siemens ./. Huawei")

	// Sanity: VCALENDAR boundaries.
	if !strings.HasPrefix(out, "BEGIN:VCALENDAR\r\n") {
		t.Fatalf("missing VCALENDAR start: %q", firstLines(out, 3))
	}
	if !strings.HasSuffix(out, "END:VCALENDAR\r\n") {
		t.Errorf("missing VCALENDAR end")
	}

	// Should emit exactly 2 VEVENTs (1 deadline + 1 appointment), nothing for the 3 skipped kinds.
	if got := strings.Count(out, "BEGIN:VEVENT"); got != 2 {
		t.Errorf("VEVENT count = %d, want 2 (deadline + appointment only)", got)
	}

	// Deadline → VALUE=DATE.
	if !strings.Contains(out, "DTSTART;VALUE=DATE:20260615") {
		t.Errorf("deadline DTSTART should be all-day VALUE=DATE format; got:\n%s", out)
	}
	// Appointment → UTC timestamp.
	if !strings.Contains(out, "DTSTART:20260701T093000Z") {
		t.Errorf("appointment DTSTART should be UTC timestamp; got:\n%s", out)
	}

	// UIDs distinct + namespaced.
	if !strings.Contains(out, "UID:paliad-deadline-"+dID.String()+"@paliad.de") {
		t.Errorf("missing canonical deadline UID")
	}
	if !strings.Contains(out, "UID:paliad-appointment-"+aID.String()+"@paliad.de") {
		t.Errorf("missing canonical appointment UID")
	}

	// X-WR-CALNAME from project title (escaped — ' . / ' contains no special chars but check it's there).
	if !strings.Contains(out, "X-WR-CALNAME:Paliad — Siemens ./. Huawei") {
		t.Errorf("X-WR-CALNAME missing or wrong: searching in:\n%s", out)
	}
}

func TestFormatTimelineICS_UndatedRowsSkipped(t *testing.T) {
	dID := uuid.New()
	events := []TimelineEvent{
		{Kind: "deadline", Status: "open", Date: nil, Title: "Datum offen", DeadlineID: &dID},
	}
	out := FormatTimelineICS(events, "X")
	if strings.Contains(out, "BEGIN:VEVENT") {
		t.Errorf("undated deadlines must not emit a VEVENT (no DTSTART would be invalid iCal)")
	}
}

func TestFormatTimelineICS_TitleEscaping(t *testing.T) {
	due := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
	dID := uuid.New()
	events := []TimelineEvent{
		{
			Kind: "deadline", Status: "open", Date: &due,
			Title:       `Frist mit ; und , und \ und ` + "\n" + "Newline",
			Description: `Beschreibung mit Komma,`,
			DeadlineID:  &dID,
		},
	}
	out := FormatTimelineICS(events, "")
	// RFC 5545 §3.3.11: ; , \ \n must be escaped.
	if !strings.Contains(out, `\;`) {
		t.Errorf("missing escaped semicolon")
	}
	if !strings.Contains(out, `\,`) {
		t.Errorf("missing escaped comma")
	}
	if !strings.Contains(out, `\\`) {
		t.Errorf("missing escaped backslash")
	}
	if !strings.Contains(out, `\n`) {
		t.Errorf("missing escaped newline")
	}
}

func TestFormatTimelineICS_EmptyInputProducesValidEmptyCalendar(t *testing.T) {
	out := FormatTimelineICS(nil, "Empty Matter")
	if !strings.HasPrefix(out, "BEGIN:VCALENDAR\r\n") {
		t.Errorf("empty input should still produce a valid VCALENDAR header")
	}
	if strings.Contains(out, "BEGIN:VEVENT") {
		t.Errorf("empty input should produce 0 VEVENTs")
	}
	if !strings.HasSuffix(out, "END:VCALENDAR\r\n") {
		t.Errorf("empty input should still close the VCALENDAR")
	}
}

func firstLines(s string, n int) string {
	parts := strings.SplitN(s, "\r\n", n+1)
	return strings.Join(parts[:min(n, len(parts))], "\r\n")
}
