package service

import (
	"testing"
	"time"

	"mal/integrations/jikan"
	"mal/internal/domain"
)

func TestMergeEpisodesFiltersProviderBackedJikanToAvailableNumbers(t *testing.T) {
	episodes := mergeEpisodes([]jikan.Episode{
		{Episode: "1", Title: "Available"},
		{Episode: "2", Title: "Unavailable"},
		{Episode: "3", Title: "Dubbed"},
	}, domain.EpisodeAvailability{
		Sub: []int{1},
		Dub: []int{3},
	}, 0)

	if len(episodes) != 2 {
		t.Fatalf("len(episodes) = %d, want 2", len(episodes))
	}
	assertEpisode(t, episodes[0], 1, "Available", true, false, true, false)
	assertEpisode(t, episodes[1], 3, "Dubbed", false, true, false, false)
}

func TestMergeEpisodesHonorsExpectedCountForAvailability(t *testing.T) {
	episodes := mergeEpisodes(nil, domain.EpisodeAvailability{
		Sub: []int{0, 1, 2, 4},
		Dub: []int{-1, 2, 3, 5},
	}, 3)

	if len(episodes) != 3 {
		t.Fatalf("len(episodes) = %d, want 3", len(episodes))
	}
	assertEpisode(t, episodes[0], 1, "Episode 1", true, false, true, false)
	assertEpisode(t, episodes[1], 2, "Episode 2", true, true, false, false)
	assertEpisode(t, episodes[2], 3, "Episode 3", false, true, false, false)
}

func TestIsCanonicalEpisodePayloadValidAllowsProviderPayloadWhenNoExpectedCount(t *testing.T) {
	payload := domain.CanonicalEpisodeList{
		Source: "AllAnime",
		Episodes: []domain.CanonicalEpisode{
			{Number: 1, HasSub: true},
			{Number: 2, HasDub: true},
		},
	}

	if !isCanonicalEpisodePayloadValid(payload, 0) {
		t.Fatalf("expected provider-backed payload with availability to be valid")
	}
}

func TestIsCanonicalEpisodePayloadValidRejectsOutOfRangeEpisodeNumber(t *testing.T) {
	payload := domain.CanonicalEpisodeList{
		Episodes: []domain.CanonicalEpisode{
			{Number: 0, Title: "Invalid"},
		},
	}

	if isCanonicalEpisodePayloadValid(payload, 12) {
		t.Fatalf("expected zero episode number to be invalid")
	}
}

func TestNextRefreshAtForFinishedAnimeIsEmpty(t *testing.T) {
	now := time.Date(2026, 5, 16, 13, 0, 0, 0, time.UTC)
	got := nextRefreshAt(domain.Anime{Anime: jikan.Anime{Airing: false}}, now)

	if got.Valid {
		t.Fatalf("nextRefreshAt finished anime = %s, want invalid", got.Time)
	}
}

func TestNextRefreshAtRetriesSoonAfterRecentBroadcast(t *testing.T) {
	anime := domain.Anime{Anime: jikan.Anime{Airing: true}}
	anime.Broadcast.Day = "Saturdays"
	anime.Broadcast.Time = "12:00"
	anime.Broadcast.Timezone = "UTC"
	now := time.Date(2026, 5, 16, 13, 0, 0, 0, time.UTC)

	got := nextRefreshAt(anime, now)
	want := now.Add(retryInterval).UTC()
	if !got.Valid || !got.Time.Equal(want) {
		t.Fatalf("nextRefreshAt = %#v, want %s", got, want)
	}
}

func TestNextRefreshAtFallsBackWhenBroadcastMetadataMissing(t *testing.T) {
	anime := domain.Anime{Anime: jikan.Anime{Airing: true}}
	now := time.Date(2026, 5, 16, 13, 0, 0, 0, time.UTC)

	got := nextRefreshAt(anime, now)
	want := now.Add(airingFallbackRefreshInterval).UTC()
	if !got.Valid || !got.Time.Equal(want) {
		t.Fatalf("nextRefreshAt = %#v, want %s", got, want)
	}
}

func TestBroadcastHelpersRejectInvalidValues(t *testing.T) {
	if day := weekdayFromJikan("someday"); day != -1 {
		t.Fatalf("weekdayFromJikan invalid = %d, want -1", day)
	}
	if _, _, ok := parseBroadcastTime("25:99"); ok {
		t.Fatalf("parseBroadcastTime should reject invalid time")
	}

	anime := domain.Anime{Anime: jikan.Anime{MalID: 1}}
	anime.Broadcast.Day = "Saturdays"
	anime.Broadcast.Time = "bad"
	if got := nextBroadcastAfter(anime, time.Date(2026, 5, 16, 13, 0, 0, 0, time.UTC)); !got.IsZero() {
		t.Fatalf("nextBroadcastAfter invalid time = %s, want zero", got)
	}
}
