package api

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/ponzischeme89/memby/server/internal/sonarr"
)

func TestBuildSonarrRowIncludesScheduleAndAddedState(t *testing.T) {
	location := time.FixedZone("NZST", 12*60*60)
	air := time.Date(2026, 7, 27, 8, 0, 0, 0, time.UTC)
	added := time.Date(2026, 7, 27, 8, 12, 0, 0, time.UTC)
	row, err := buildSonarrRow([]sonarr.Episode{{
		ID:            42,
		SeriesID:      7,
		SeasonNumber:  2,
		EpisodeNumber: 4,
		Title:         "The Crossing",
		AirDateUTC:    &air,
		HasFile:       true,
		Monitored:     true,
		EpisodeFile:   &sonarr.EpisodeFile{DateAdded: &added},
		Series: sonarr.Series{
			ID:    7,
			Title: "Northbound",
			Images: []sonarr.Image{
				{CoverType: "poster"},
				{CoverType: "fanart"},
			},
		},
	}}, time.Date(2026, 7, 27, 21, 0, 0, 0, location), location, nil)
	if err != nil {
		t.Fatal(err)
	}
	if row.ID != "sonarr-airing-today" || row.Kind != "schedule" || len(row.Items) != 1 {
		t.Fatalf("unexpected row: %+v", row)
	}
	var item sonarrScheduleItem
	if err := json.Unmarshal(row.Items[0], &item); err != nil {
		t.Fatal(err)
	}
	if item.ID != "sonarr:7:42" || item.MembyEpisodeCode != "S02E04" {
		t.Fatalf("unexpected identity: %+v", item)
	}
	if item.MembyAvailability != "available" || item.MembyAvailabilityText != "Added at 8:12 PM" {
		t.Fatalf("unexpected availability: %+v", item)
	}
	if item.ImageTags["Primary"] == "" || len(item.BackdropImageTags) != 1 {
		t.Fatalf("artwork was not exposed: %+v", item)
	}
}

func TestBuildSonarrRowLinksTheEmbySeries(t *testing.T) {
	location := time.UTC
	now := time.Date(2026, 7, 27, 8, 0, 0, 0, location)
	air := time.Date(2026, 7, 27, 20, 0, 0, 0, location)
	index := seriesIndex{
		"northbound":      {ID: "emby-old"},
		"northbound|2024": {ID: "emby-2024", LogoTag: "northbound-logo"},
		"harbour":         {ID: "emby-harbour"},
	}
	episode := func(title string, year int) sonarr.Episode {
		utc := air
		return sonarr.Episode{
			ID: 1, SeriesID: 1, SeasonNumber: 1, EpisodeNumber: 1, AirDateUTC: &utc,
			Series: sonarr.Series{ID: 1, Title: title, Year: year},
		}
	}
	cases := map[string]struct {
		episode sonarr.Episode
		want    string
	}{
		// The year decides between a remake and its original.
		"year qualified": {episode("Northbound", 2024), "emby-2024"},
		// Sonarr and Emby disagree about a year more often than about a name.
		"year mismatch falls back to the title": {episode("Northbound", 1999), "emby-old"},
		"punctuation and case are ignored":      {episode("Harbour!", 0), "emby-harbour"},
		"not in the library":                    {episode("Unimported", 2026), ""},
	}
	for name, testCase := range cases {
		t.Run(name, func(t *testing.T) {
			row, err := buildSonarrRow([]sonarr.Episode{testCase.episode}, now, location, index)
			if err != nil {
				t.Fatal(err)
			}
			var item sonarrScheduleItem
			if err := json.Unmarshal(row.Items[0], &item); err != nil {
				t.Fatal(err)
			}
			if item.MembySeriesItemID != testCase.want {
				t.Fatalf("series id = %q, want %q", item.MembySeriesItemID, testCase.want)
			}
		})
	}
}

func TestScheduleEpisodeEventNamesPremieresAndFinales(t *testing.T) {
	tests := []struct {
		name    string
		episode sonarr.Episode
		want    string
	}{
		{"season premiere", sonarr.Episode{SeasonNumber: 3, EpisodeNumber: 1}, "Season premiere"},
		{"season finale", sonarr.Episode{SeasonNumber: 3, EpisodeNumber: 8, FinaleType: "seasonFinale"}, "Season finale"},
		{"series finale", sonarr.Episode{SeasonNumber: 5, EpisodeNumber: 10, FinaleType: "seriesFinale"}, "Series finale"},
		{"Sonarr season value", sonarr.Episode{SeasonNumber: 3, EpisodeNumber: 8, FinaleType: "season"}, "Season finale"},
		{"mid-season finale", sonarr.Episode{SeasonNumber: 3, EpisodeNumber: 5, FinaleType: "midseason"}, "Mid-season finale"},
		{"ordinary episode", sonarr.Episode{SeasonNumber: 3, EpisodeNumber: 4}, ""},
		{"special", sonarr.Episode{SeasonNumber: 0, EpisodeNumber: 1}, ""},
	}
	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			if got := scheduleEpisodeEvent(testCase.episode); got != testCase.want {
				t.Fatalf("event = %q, want %q", got, testCase.want)
			}
		})
	}
}

func TestScheduleItemCarriesMatchedEmbyLogo(t *testing.T) {
	episode := sonarr.Episode{
		ID: 1, SeriesID: 7, SeasonNumber: 2, EpisodeNumber: 1,
		Series: sonarr.Series{ID: 7, Title: "Northbound", Year: 2024},
	}
	item := toSonarrScheduleItem(episode, time.Now(), time.UTC, seriesIndex{
		"northbound|2024": {ID: "emby-series", LogoTag: "logo-tag"},
	})
	if item.ParentLogoItemID != "emby-series" || item.ParentLogoImageTag != "logo-tag" {
		t.Fatalf("matched logo was not carried: %+v", item)
	}
}

func TestBuildPrerollScheduleSplitsTodayAndWeek(t *testing.T) {
	location := time.FixedZone("NZST", 12*60*60)
	now := time.Date(2026, 7, 28, 10, 0, 0, 0, location)
	today := time.Date(2026, 7, 28, 20, 30, 0, 0, location).UTC()
	thursday := time.Date(2026, 7, 30, 19, 0, 0, 0, location).UTC()
	schedule := buildPrerollSchedule([]sonarr.Episode{
		{
			ID: 14, SeriesID: 7, SeasonNumber: 1, EpisodeNumber: 4, Title: "Tonight",
			AirDateUTC: &today, Series: sonarr.Series{
				ID: 7, Title: "Northbound", Images: []sonarr.Image{{CoverType: "fanart"}},
			},
		},
		{
			SeasonNumber: 2, EpisodeNumber: 1, Title: "Later",
			AirDateUTC: &thursday, HasFile: true, Series: sonarr.Series{Title: "Harbour"},
		},
	}, now, location)

	if len(schedule.Today) != 1 || schedule.Today[0].Series != "Northbound" ||
		schedule.Today[0].Schedule != "8:30 PM" || schedule.Today[0].ItemID != "sonarr:7:14" ||
		schedule.Today[0].ImageType != "backdrop" {
		t.Fatalf("unexpected today schedule: %+v", schedule.Today)
	}
	if len(schedule.ThisWeek) != 1 || schedule.ThisWeek[0].EpisodeCode != "S02E01" ||
		schedule.ThisWeek[0].Availability != "Downloaded" {
		t.Fatalf("unexpected week schedule: %+v", schedule.ThisWeek)
	}
}

func TestBuildSonarrRowCoversFiveDaysAndUsesRelativeAirLabels(t *testing.T) {
	location := time.FixedZone("NZST", 12*60*60)
	now := time.Date(2026, 7, 29, 8, 0, 0, 0, location)
	episode := func(id int, air time.Time) sonarr.Episode {
		utc := air.UTC()
		return sonarr.Episode{
			ID: id, SeriesID: id, SeasonNumber: 1, EpisodeNumber: id,
			Title: "Episode", AirDateUTC: &utc,
			Series: sonarr.Series{ID: id, Title: "Show"},
		}
	}
	row, err := buildSonarrRow([]sonarr.Episode{
		// Sonarr's response order is not part of its contract. Deliberately put the
		// later episode first to ensure the row is authored chronologically.
		episode(3, time.Date(2026, 8, 2, 16, 0, 0, 0, location)),
		episode(2, time.Date(2026, 7, 30, 16, 0, 0, 0, location)),
		episode(1, time.Date(2026, 7, 29, 16, 0, 0, 0, location)),
		episode(4, time.Date(2026, 8, 3, 16, 0, 0, 0, location)),
	}, now, location, nil)
	if err != nil {
		t.Fatal(err)
	}
	if row.Title != "Shows airing in the next 5 days" || len(row.Items) != 3 {
		t.Fatalf("five-day row = %+v", row)
	}
	labels := make([]string, 0, len(row.Items))
	dayLabels := make([]string, 0, len(row.Items))
	for _, raw := range row.Items {
		var item sonarrScheduleItem
		if err := json.Unmarshal(raw, &item); err != nil {
			t.Fatal(err)
		}
		labels = append(labels, item.MembyAirLabel)
		dayLabels = append(dayLabels, item.MembyAirDayLabel)
	}
	want := []string{
		"In 8 hours (4:00 PM)",
		"Tomorrow: 4:00 PM",
		"Sunday: 4:00 PM",
	}
	for i := range want {
		if labels[i] != want[i] {
			t.Fatalf("labels = %#v, want %#v", labels, want)
		}
	}
	wantDays := []string{"Today", "Tomorrow", "Sunday"}
	for i := range wantDays {
		if dayLabels[i] != wantDays[i] {
			t.Fatalf("day labels = %#v, want %#v", dayLabels, wantDays)
		}
	}
}
