package translator

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

func TestLibreTranslateHappyPath(t *testing.T) {
	t.Parallel()
	var (
		requestSource string
		requestTarget string
		requestQ      []string
		requestFormat string
	)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, _ := io.ReadAll(r.Body)
		var in requestBody
		if err := json.Unmarshal(body, &in); err != nil {
			t.Errorf("unmarshal: %v", err)
		}
		requestSource = in.Source
		requestTarget = in.Target
		requestQ = in.Q
		requestFormat = in.Format
		_ = json.NewEncoder(w).Encode(responseBody{
			TranslatedText: []string{"[ru] " + in.Q[0], "[ru] " + in.Q[1]},
		})
	}))
	t.Cleanup(server.Close)

	tr, err := NewLibreTranslate(LibreTranslateConfig{URL: server.URL, Timeout: 2 * time.Second})
	if err != nil {
		t.Fatalf("new: %v", err)
	}
	res, err := tr.Translate(context.Background(), "en", "ru", "Hello", "World")
	if err != nil {
		t.Fatalf("translate: %v", err)
	}
	if res.Engine != LibreTranslateEngine {
		t.Fatalf("engine = %q, want %q", res.Engine, LibreTranslateEngine)
	}
	if res.Subject != "[ru] Hello" || res.Body != "[ru] World" {
		t.Fatalf("result = %+v", res)
	}
	if requestSource != "en" || requestTarget != "ru" || requestFormat != "text" {
		t.Fatalf("request fields: src=%q dst=%q fmt=%q", requestSource, requestTarget, requestFormat)
	}
	if len(requestQ) != 2 || requestQ[0] != "Hello" || requestQ[1] != "World" {
		t.Fatalf("request q = %v", requestQ)
	}
}

func TestLibreTranslateNormalisesLanguageCodes(t *testing.T) {
	t.Parallel()
	var src, dst string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, _ := io.ReadAll(r.Body)
		var in requestBody
		_ = json.Unmarshal(body, &in)
		src, dst = in.Source, in.Target
		_ = json.NewEncoder(w).Encode(responseBody{TranslatedText: []string{"a", "b"}})
	}))
	t.Cleanup(server.Close)

	tr, _ := NewLibreTranslate(LibreTranslateConfig{URL: server.URL})
	if _, err := tr.Translate(context.Background(), "EN-US", "ru-RU", "x", "y"); err != nil {
		t.Fatalf("translate: %v", err)
	}
	if src != "en" || dst != "ru" {
		t.Fatalf("normalised codes src=%q dst=%q, want en/ru", src, dst)
	}
}

func TestLibreTranslateUnsupportedPair(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusBadRequest)
		_, _ = w.Write([]byte(`{"error":"language not supported"}`))
	}))
	t.Cleanup(server.Close)

	tr, _ := NewLibreTranslate(LibreTranslateConfig{URL: server.URL})
	_, err := tr.Translate(context.Background(), "en", "xx", "subject", "body")
	if !errors.Is(err, ErrUnsupportedLanguagePair) {
		t.Fatalf("err = %v, want ErrUnsupportedLanguagePair", err)
	}
}

func TestLibreTranslateServerError(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("kaboom"))
	}))
	t.Cleanup(server.Close)

	tr, _ := NewLibreTranslate(LibreTranslateConfig{URL: server.URL})
	_, err := tr.Translate(context.Background(), "en", "ru", "subject", "body")
	if err == nil {
		t.Fatalf("expected error, got nil")
	}
	if errors.Is(err, ErrUnsupportedLanguagePair) {
		t.Fatalf("err mis-classified as unsupported pair: %v", err)
	}
	if !strings.Contains(err.Error(), "500") {
		t.Fatalf("err = %v, want mention of 500", err)
	}
}

func TestLibreTranslateSameSourceAndTargetIsNoop(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Errorf("translator should not call the server for identical src/dst: %s", r.URL.Path)
	}))
	t.Cleanup(server.Close)

	tr, _ := NewLibreTranslate(LibreTranslateConfig{URL: server.URL})
	res, err := tr.Translate(context.Background(), "en", "EN", "x", "y")
	if err != nil {
		t.Fatalf("translate: %v", err)
	}
	if res.Engine != NoopEngine {
		t.Fatalf("engine = %q, want %q", res.Engine, NoopEngine)
	}
}

func TestLibreTranslateRequiresURL(t *testing.T) {
	t.Parallel()
	_, err := NewLibreTranslate(LibreTranslateConfig{URL: ""})
	if err == nil {
		t.Fatalf("expected error for empty URL")
	}
}

// TestLibreTranslateRejectsMalformedArray defends against a server
// that returns a partial / unexpected `translatedText` payload. The
// client must surface an error (not panic, not return a half-empty
// Result) so the worker can decide between retry and fallback.
func TestLibreTranslateRejectsMalformedArray(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name string
		body string
	}{
		{"single string", `{"translatedText": "only one"}`},
		{"array of one", `{"translatedText": ["only one"]}`},
		{"empty array", `{"translatedText": []}`},
		{"missing field", `{"foo":"bar"}`},
	}
	for _, tc := range cases {
		body := tc.body
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
				_, _ = w.Write([]byte(body))
			}))
			t.Cleanup(server.Close)
			tr, _ := NewLibreTranslate(LibreTranslateConfig{URL: server.URL})
			res, err := tr.Translate(context.Background(), "en", "ru", "subject", "body")
			if err == nil {
				t.Fatalf("expected error for malformed body %q, got %+v", body, res)
			}
		})
	}
}
