package api

import (
	"testing"

	"github.com/ponzischeme89/memby/server/internal/bazarr"
	"github.com/ponzischeme89/memby/server/internal/opensubtitles"
	"github.com/ponzischeme89/memby/server/internal/store"
)

// A row has to carry the backend it came from, because the two tokens are opaque in
// different ways and handing one to the other is a mistake nothing downstream could catch.
func TestCandidatesCarryTheirSource(t *testing.T) {
	fromBazarr := bazarrCandidates([]bazarr.Subtitle{{Language: "eng", Token: "opaque"}})
	if len(fromBazarr) != 1 || fromBazarr[0].Source != store.SubtitleProviderBazarr {
		t.Fatalf("bazarr candidate = %+v", fromBazarr)
	}
	fromOpen := openSubtitlesCandidates([]opensubtitles.Subtitle{{FileID: 42, Language: "en"}})
	if len(fromOpen) != 1 || fromOpen[0].Source != store.SubtitleProviderOpenSubtitles {
		t.Fatalf("opensubtitles candidate = %+v", fromOpen)
	}
	if fromOpen[0].Token != "42" {
		t.Fatalf("token = %q, want the file id", fromOpen[0].Token)
	}
}

// A machine translation is a real answer and sometimes the only one, so it is offered —
// below everything a person wrote, whatever confidence the provider claims for it.
func TestRankingSinksMachineTranslationsBelowHumanOnes(t *testing.T) {
	found := []subtitleCandidate{
		{Source: "opensubtitles", Token: "robot", Language: "en", Score: 99, MachineOnly: true},
		{Source: "bazarr", Token: "human", Language: "en", Score: 40},
	}
	results := rankMergedCandidates(found, "en")
	if results[0].Token != "human" {
		t.Fatalf("order = %q, %q", results[0].Token, results[1].Token)
	}
	if want := "English · Machine translated · 99% match"; results[1].Label != want {
		t.Fatalf("label = %q, want %q", results[1].Label, want)
	}
}

// The viewer's language still outranks everything, across providers as it did within one.
func TestRankingKeepsTheChosenLanguageFirstAcrossProviders(t *testing.T) {
	found := []subtitleCandidate{
		{Source: "bazarr", Token: "en", Language: "en", Score: 99},
		{Source: "opensubtitles", Token: "it", Language: "it", Score: 20},
	}
	if got := rankMergedCandidates(found, "it"); got[0].Token != "it" {
		t.Fatalf("first row = %q, want the Italian one", got[0].Token)
	}
}

// A file nobody has rated is not a bad file, so it lands mid-scale rather than at the
// bottom — the judgement heroUnratedScore already makes about an unrated title.
func TestOpenSubtitlesScoreIsMidScaleWhenUnrated(t *testing.T) {
	unrated := openSubtitlesScore(opensubtitles.Subtitle{})
	if unrated < 40 || unrated > 70 {
		t.Fatalf("unrated score = %d, want mid-scale", unrated)
	}
	if rated := openSubtitlesScore(opensubtitles.Subtitle{Rating: 9.4}); rated <= unrated {
		t.Fatalf("a well-rated file scored %d, below an unrated %d", rated, unrated)
	}
	if machine := openSubtitlesScore(opensubtitles.Subtitle{Rating: 9.4, MachineOnly: true}); machine >= 94 {
		t.Fatalf("a machine translation kept its full score (%d)", machine)
	}
}

// Two identically labelled rows in one drop-up is what makes a viewer distrust the whole
// menu, so where both exist Emby's track wins: it is the one in the file.
func TestMergeSubtitleTracksDropsWhatEmbyAlreadyHas(t *testing.T) {
	emby := []playableSubtitle{{ID: "3", Language: "eng", DeliveryMethod: "External"}}
	stored := []playableSubtitle{
		{ID: "gw:1:en:plain", Language: "en", DeliveryMethod: "External"},
		{ID: "gw:1:it:plain", Language: "it", DeliveryMethod: "External"},
	}
	merged := mergeSubtitleTracks(emby, stored)
	if len(merged) != 2 {
		t.Fatalf("merged = %+v", merged)
	}
	if merged[1].ID != "gw:1:it:plain" {
		t.Fatalf("kept %q, want only the Italian one to survive", merged[1].ID)
	}
}

// A forced track is not the same track as a plain one in the same language, so it must not
// be deduplicated away — that is exactly the subtitle somebody downloaded it for.
func TestMergeSubtitleTracksKeepsADifferentVariant(t *testing.T) {
	emby := []playableSubtitle{{ID: "3", Language: "eng"}}
	stored := []playableSubtitle{{ID: "gw:1:en:forced", Language: "en", IsForced: true}}
	if merged := mergeSubtitleTracks(emby, stored); len(merged) != 2 {
		t.Fatalf("merged = %+v", merged)
	}
}

// The id is derived from what was asked for, so fetching the same language twice replaces
// the file rather than growing a second track a viewer has to tell apart by guessing.
func TestStoredSubtitleIDIsStablePerVariant(t *testing.T) {
	plain := storedSubtitleID("42", subtitleCandidate{Language: "it"})
	again := storedSubtitleID("42", subtitleCandidate{Language: "it", Provider: "elsewhere"})
	if plain != again {
		t.Fatalf("%q != %q for the same title and language", plain, again)
	}
	forced := storedSubtitleID("42", subtitleCandidate{Language: "it", Forced: true})
	if forced == plain {
		t.Fatal("a forced track took the plain track's id")
	}
	if !isStoredSubtitleID(plain) {
		t.Fatalf("%q is not recognised as the gateway's own", plain)
	}
	// Emby's ids are stream indices, which are plain numbers. The two namespaces must not
	// be able to collide, because the player matches a track on the id it was handed.
	if isStoredSubtitleID("3") {
		t.Fatal("an Emby stream index was read as a gateway subtitle")
	}
}

// A search restricted to nothing returns every language on earth, which is unreadable on a
// television; English rides along because a household that never set a language gets one.
func TestOpenSubtitlesLanguagesAlwaysNameSomething(t *testing.T) {
	for _, language := range []string{"", "auto"} {
		if got := openSubtitlesLanguages(language); len(got) != 1 || got[0] != "en" {
			t.Fatalf("languages for %q = %v", language, got)
		}
	}
	if got := openSubtitlesLanguages("it"); len(got) != 2 || got[0] != "it" {
		t.Fatalf("languages for Italian = %v, want Italian first", got)
	}
}

// The allowance running out is the only failure where pressing the button again is
// definitely not the answer, so it must not be flattened into "did not answer".
func TestSubtitleFailureMessageSeparatesTheQuotaCase(t *testing.T) {
	if got := subtitleFailureMessage(nil); got != "No subtitles were found for this release." {
		t.Fatalf("no failures gave %q", got)
	}
	quota := subtitleFailureMessage([]error{&opensubtitles.QuotaError{}})
	other := subtitleFailureMessage([]error{&opensubtitles.APIError{StatusCode: 500}})
	if quota == other {
		t.Fatalf("an exhausted quota reads the same as any other failure: %q", quota)
	}
}
