package tts

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync/atomic"
	"testing"
)

// newStubPiper returns a fake Piper server that echoes a fixed WAV body and
// records how many times it was called and the last request payload.
func newStubPiper(t *testing.T, body []byte) (*httptest.Server, *int32, *synthEcho) {
	t.Helper()
	var calls int32
	last := &synthEcho{}
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		atomic.AddInt32(&calls, 1)
		var req map[string]any
		_ = json.NewDecoder(r.Body).Decode(&req)
		last.voice, _ = req["voice"].(string)
		last.text, _ = req["text"].(string)
		w.Header().Set("Content-Type", "audio/wav")
		_, _ = w.Write(body)
	}))
	t.Cleanup(srv.Close)
	return srv, &calls, last
}

type synthEcho struct{ voice, text string }

// newHandler builds a wav-format handler (no ffmpeg) pointed at a stub server.
func newHandler(t *testing.T, endpoint string) *Handler {
	t.Helper()
	return &Handler{
		routes:   map[string]route{"en": {strings.TrimRight(endpoint, "/"), "en_US-amy-medium"}},
		cacheDir: t.TempDir(),
		format:   formats["wav"],
		client:   http.DefaultClient,
	}
}

func post(t *testing.T, h *Handler, text, lang string) *httptest.ResponseRecorder {
	t.Helper()
	b, _ := json.Marshal(synthRequest{Text: text, Lang: lang})
	req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(b))
	rr := httptest.NewRecorder()
	h.synth(rr, req)
	return rr
}

func TestSynthSuccessAndVoiceSelection(t *testing.T) {
	wav := []byte("RIFF....fake-wav")
	srv, calls, last := newStubPiper(t, wav)
	h := newHandler(t, srv.URL)

	rr := post(t, h, "hello there", "en-US")
	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200", rr.Code)
	}
	if got := rr.Body.Bytes(); !bytes.Equal(got, wav) {
		t.Fatalf("body = %q, want the piper wav", got)
	}
	if ct := rr.Header().Get("Content-Type"); ct != "audio/wav" {
		t.Fatalf("content-type = %q, want audio/wav", ct)
	}
	if last.voice != "en_US-amy-medium" {
		t.Fatalf("piper voice = %q, want en_US-amy-medium", last.voice)
	}
	if *calls != 1 {
		t.Fatalf("piper calls = %d, want 1", *calls)
	}
}

func TestRoutesByLanguageToSeparateInstances(t *testing.T) {
	enSrv, enCalls, _ := newStubPiper(t, []byte("EN-wav"))
	zhSrv, zhCalls, zhLast := newStubPiper(t, []byte("ZH-wav"))
	h := &Handler{
		routes: map[string]route{
			"en": {strings.TrimRight(enSrv.URL, "/"), "en_US-amy-medium"},
			"zh": {strings.TrimRight(zhSrv.URL, "/"), "zh_CN-huayan-medium"},
		},
		cacheDir: t.TempDir(),
		format:   formats["wav"],
		client:   http.DefaultClient,
	}

	if rr := post(t, h, "你好世界", "zh-CN"); rr.Code != http.StatusOK {
		t.Fatalf("zh status = %d, want 200", rr.Code)
	}
	if *zhCalls != 1 || *enCalls != 0 {
		t.Fatalf("calls en=%d zh=%d, want en=0 zh=1 (zh routed to zh instance)", *enCalls, *zhCalls)
	}
	if zhLast.voice != "zh_CN-huayan-medium" {
		t.Fatalf("zh voice = %q, want zh_CN-huayan-medium", zhLast.voice)
	}
}

func TestUnknownLanguageReturns404(t *testing.T) {
	srv, calls, _ := newStubPiper(t, []byte("x"))
	h := newHandler(t, srv.URL)

	rr := post(t, h, "你好", "zh-CN") // only "en" is configured
	if rr.Code != http.StatusNotFound {
		t.Fatalf("status = %d, want 404", rr.Code)
	}
	if *calls != 0 {
		t.Fatalf("piper calls = %d, want 0 (should not synthesize)", *calls)
	}
}

func TestCacheHitSkipsPiper(t *testing.T) {
	srv, calls, _ := newStubPiper(t, []byte("RIFF....fake-wav"))
	h := newHandler(t, srv.URL)

	if rr := post(t, h, "same words", "en-US"); rr.Code != http.StatusOK {
		t.Fatalf("first status = %d, want 200", rr.Code)
	}
	if rr := post(t, h, "same words", "en-US"); rr.Code != http.StatusOK {
		t.Fatalf("second status = %d, want 200", rr.Code)
	}
	if *calls != 1 {
		t.Fatalf("piper calls = %d, want 1 (second served from cache)", *calls)
	}
}

func TestEmptyTextReturns400(t *testing.T) {
	srv, _, _ := newStubPiper(t, []byte("x"))
	h := newHandler(t, srv.URL)

	if rr := post(t, h, "   ", "en-US"); rr.Code != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400", rr.Code)
	}
}

func TestTextIsCapped(t *testing.T) {
	srv, _, last := newStubPiper(t, []byte("x"))
	h := newHandler(t, srv.URL)

	long := strings.Repeat("a", maxTextBytes+500)
	if rr := post(t, h, long, "en-US"); rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200", rr.Code)
	}
	if len(last.text) != maxTextBytes {
		t.Fatalf("piper received %d chars, want capped to %d", len(last.text), maxTextBytes)
	}
}

func TestBaseLang(t *testing.T) {
	cases := map[string]string{"en-US": "en", "EN_gb": "en", "zh-CN": "zh", "en": "en", "": ""}
	for in, want := range cases {
		if got := baseLang(in); got != want {
			t.Errorf("baseLang(%q) = %q, want %q", in, got, want)
		}
	}
}

// ensure the stub's body is fully consumable (guards against the LimitReader cap
// accidentally truncating a normal request body in synth()).
func TestRequestBodyNotTruncated(t *testing.T) {
	srv, _, last := newStubPiper(t, []byte("x"))
	h := newHandler(t, srv.URL)
	text := strings.Repeat("word ", 200) // ~1000 bytes, under the cap
	post(t, h, text, "en-US")
	if last.text != strings.TrimSpace(text) {
		t.Fatalf("piper text length = %d, want %d", len(last.text), len(strings.TrimSpace(text)))
	}
}
