package bazarr

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"
)

func TestSearchMovieSendsRadarrIDAndKeyInHeader(t *testing.T) {
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/providers/movies" {
			t.Errorf("path = %q", r.URL.Path)
		}
		if got := r.Header.Get("X-API-KEY"); got != "secret" {
			t.Errorf("X-API-KEY = %q", got)
		}
		if got := r.URL.Query().Get("radarrid"); got != "42" {
			t.Errorf("radarrid = %q", got)
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`[
			{"language":"en","forced":false,"hearing_impaired":"False","provider":"opensubtitles",
			 "subtitle":"opaque-token","score":97,"release_info":["Arrival.2016.1080p"],"uploader":"someone"}
		]`))
	}))
	defer upstream.Close()

	subtitles, err := New(upstream.URL, "secret", time.Second).SearchMovie(context.Background(), 42)
	if err != nil {
		t.Fatal(err)
	}
	if len(subtitles) != 1 {
		t.Fatalf("subtitles = %+v", subtitles)
	}
	got := subtitles[0]
	if got.Language != "en" || got.Score != 97 || got.Token != "opaque-token" {
		t.Fatalf("unexpected subtitle: %+v", got)
	}
	if got.HearingImpaired {
		t.Error(`hearing_impaired "False" decoded as true`)
	}
}

// Bazarr has shipped both shapes for these fields across versions. Decoding either is what
// stops an upgrade from silently turning every result into a plain, non-forced track.
func TestSubtitleDecodesBoolAndStringFlagsAndBothLanguageShapes(t *testing.T) {
	for _, payload := range []struct {
		name string
		json string
	}{
		{"strings", `{"language":"it","forced":"True","hearing_impaired":"true","subtitle":"t"}`},
		{"bools", `{"language":"it","forced":true,"hearing_impaired":true,"subtitle":"t"}`},
		{"language object", `{"language":{"code2":"it","name":"Italian"},"forced":true,"hearing_impaired":1,"subtitle":"t"}`},
	} {
		t.Run(payload.name, func(t *testing.T) {
			var subtitle Subtitle
			if err := json.Unmarshal([]byte(payload.json), &subtitle); err != nil {
				t.Fatal(err)
			}
			if subtitle.Language != "it" {
				t.Errorf("language = %q", subtitle.Language)
			}
			if !subtitle.Forced {
				t.Error("forced = false")
			}
			// `1` is a number, not one of the accepted strings, so the last case is
			// deliberately allowed to be false — the point is that it decodes at all.
			if payload.name != "language object" && !subtitle.HearingImpaired {
				t.Error("hearing_impaired = false")
			}
		})
	}
}

// The result row is opaque and has to be handed back exactly as it arrived; reconstructing
// it from the parsed fields is the mistake this pins against.
func TestDownloadMoviePostsTheTokenVerbatim(t *testing.T) {
	var form url.Values
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost || r.URL.Path != "/api/providers/movies" {
			t.Errorf("%s %s", r.Method, r.URL.Path)
		}
		if err := r.ParseForm(); err != nil {
			t.Fatal(err)
		}
		form = r.PostForm
		w.WriteHeader(http.StatusNoContent)
	}))
	defer upstream.Close()

	err := New(upstream.URL, "secret", time.Second).DownloadMovie(context.Background(), 42, Subtitle{
		Language: "en", Provider: "opensubtitles", Token: "opaque {token} with spaces", HearingImpaired: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	if got := form.Get("subtitle"); got != "opaque {token} with spaces" {
		t.Errorf("subtitle = %q", got)
	}
	if got := form.Get("radarrid"); got != "42" {
		t.Errorf("radarrid = %q", got)
	}
	if got := form.Get("hi"); got != "true" {
		t.Errorf("hi = %q", got)
	}
	if got := form.Get("forced"); got != "false" {
		t.Errorf("forced = %q", got)
	}
}

func TestDownloadEpisodeCarriesBothIDs(t *testing.T) {
	var form url.Values
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_ = r.ParseForm()
		form = r.PostForm
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(``))
	}))
	defer upstream.Close()

	err := New(upstream.URL, "secret", time.Second).
		DownloadEpisode(context.Background(), 8, 91, Subtitle{Language: "en", Token: "t"})
	if err != nil {
		t.Fatal(err)
	}
	if form.Get("seriesid") != "8" || form.Get("episodeid") != "91" {
		t.Errorf("form = %v", form)
	}
}

func TestListingsUnwrapTheDataEnvelopeAndAskForEverything(t *testing.T) {
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if got := r.URL.Query().Get("length"); got != "-1" && r.URL.Path != "/api/episodes" {
			t.Errorf("length = %q for %s", got, r.URL.Path)
		}
		w.Header().Set("Content-Type", "application/json")
		switch r.URL.Path {
		case "/api/movies":
			_, _ = w.Write([]byte(`{"data":[{"radarrId":42,"title":"Arrival","year":"2016"}]}`))
		case "/api/series":
			_, _ = w.Write([]byte(`{"data":[{"sonarrSeriesId":8,"title":"Severance","year":"2022"}]}`))
		case "/api/episodes":
			if got := r.URL.Query().Get("seriesid[]"); got != "8" {
				t.Errorf("seriesid[] = %q", got)
			}
			_, _ = w.Write([]byte(`{"data":[{"sonarrEpisodeId":91,"sonarrSeriesId":8,"season":3,"episode":4}]}`))
		}
	}))
	defer upstream.Close()

	client := New(upstream.URL, "secret", time.Second)
	movies, err := client.Movies(context.Background())
	if err != nil || len(movies) != 1 || movies[0].RadarrID != 42 {
		t.Fatalf("movies = %+v, err = %v", movies, err)
	}
	series, err := client.Series(context.Background())
	if err != nil || len(series) != 1 || series[0].SonarrSeriesID != 8 {
		t.Fatalf("series = %+v, err = %v", series, err)
	}
	episodes, err := client.Episodes(context.Background(), 8)
	if err != nil || len(episodes) != 1 || episodes[0].SonarrEpisodeID != 91 {
		t.Fatalf("episodes = %+v, err = %v", episodes, err)
	}
}

func TestUpstreamFailureCarriesTheStatus(t *testing.T) {
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "no providers enabled", http.StatusBadRequest)
	}))
	defer upstream.Close()

	_, err := New(upstream.URL, "secret", time.Second).SearchMovie(context.Background(), 1)
	apiErr, ok := err.(*APIError)
	if !ok {
		t.Fatalf("err = %T %v", err, err)
	}
	if apiErr.StatusCode != http.StatusBadRequest {
		t.Errorf("status = %d", apiErr.StatusCode)
	}
}
