package kokoro_test

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/libnovel/backend/internal/kokoro"
)

// ── VoiceSampleKey ────────────────────────────────────────────────────────────

func TestVoiceSampleKey(t *testing.T) {
	tests := []struct {
		voice string
		want  string
	}{
		{"af_bella", "_voice-samples/af_bella.mp3"},
		{"am_echo", "_voice-samples/am_echo.mp3"},
		{"voice with spaces", "_voice-samples/voice_with_spaces.mp3"},
		{"special!@#chars", "_voice-samples/special___chars.mp3"},
		{"", "_voice-samples/.mp3"},
	}
	for _, tt := range tests {
		t.Run(tt.voice, func(t *testing.T) {
			got := kokoro.VoiceSampleKey(tt.voice)
			if got != tt.want {
				t.Errorf("VoiceSampleKey(%q) = %q, want %q", tt.voice, got, tt.want)
			}
		})
	}
}

// ── GenerateAudio ─────────────────────────────────────────────────────────────

func TestGenerateAudio_EmptyText(t *testing.T) {
	srv := httptest.NewServer(http.NotFoundHandler())
	defer srv.Close()

	c := kokoro.New(srv.URL)
	_, err := c.GenerateAudio(context.Background(), "", "af_bella")
	if err == nil {
		t.Fatal("expected error for empty text, got nil")
	}
	if !strings.Contains(err.Error(), "empty text") {
		t.Errorf("expected 'empty text' in error, got: %v", err)
	}
}

func TestGenerateAudio_DefaultVoice(t *testing.T) {
	// Tracks that the voice defaults to af_bella when empty.
	var capturedBody string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/v1/audio/speech" {
			buf := make([]byte, 512)
			n, _ := r.Body.Read(buf)
			capturedBody = string(buf[:n])
			w.Header().Set("X-Download-Path", "/download/test_file.mp3")
			w.WriteHeader(http.StatusOK)
			return
		}
		if strings.HasPrefix(r.URL.Path, "/v1/download/") {
			w.WriteHeader(http.StatusOK)
			_, _ = w.Write([]byte("fake-mp3-data"))
			return
		}
		http.NotFound(w, r)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	data, err := c.GenerateAudio(context.Background(), "hello world", "")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if string(data) != "fake-mp3-data" {
		t.Errorf("unexpected data: %q", string(data))
	}
	if !strings.Contains(capturedBody, `"af_bella"`) {
		t.Errorf("expected default voice af_bella in request body, got: %s", capturedBody)
	}
}

func TestGenerateAudio_SpeechNon200(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/v1/audio/speech" {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		http.NotFound(w, r)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	_, err := c.GenerateAudio(context.Background(), "text", "af_bella")
	if err == nil {
		t.Fatal("expected error for non-200 speech response")
	}
	if !strings.Contains(err.Error(), "500") {
		t.Errorf("expected 500 in error, got: %v", err)
	}
}

func TestGenerateAudio_NoDownloadPathHeader(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/v1/audio/speech" {
			// No X-Download-Path header
			w.WriteHeader(http.StatusOK)
			return
		}
		http.NotFound(w, r)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	_, err := c.GenerateAudio(context.Background(), "text", "af_bella")
	if err == nil {
		t.Fatal("expected error for missing X-Download-Path")
	}
	if !strings.Contains(err.Error(), "X-Download-Path") {
		t.Errorf("expected X-Download-Path in error, got: %v", err)
	}
}

func TestGenerateAudio_DownloadFails(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/v1/audio/speech" {
			w.Header().Set("X-Download-Path", "/v1/download/speech.mp3")
			w.WriteHeader(http.StatusOK)
			return
		}
		if strings.HasPrefix(r.URL.Path, "/v1/download/") {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		http.NotFound(w, r)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	_, err := c.GenerateAudio(context.Background(), "text", "af_bella")
	if err == nil {
		t.Fatal("expected error for failed download")
	}
	if !strings.Contains(err.Error(), "404") {
		t.Errorf("expected 404 in error, got: %v", err)
	}
}

func TestGenerateAudio_FullPath(t *testing.T) {
	// X-Download-Path with a full path: extract just filename.
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/v1/audio/speech" {
			w.Header().Set("X-Download-Path", "/some/nested/path/audio_abc123.mp3")
			w.WriteHeader(http.StatusOK)
			return
		}
		if r.URL.Path == "/v1/download/audio_abc123.mp3" {
			_, _ = w.Write([]byte("audio-bytes"))
			return
		}
		http.NotFound(w, r)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	data, err := c.GenerateAudio(context.Background(), "text", "af_bella")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if string(data) != "audio-bytes" {
		t.Errorf("unexpected data: %q", string(data))
	}
}

func TestGenerateAudio_ContextCancelled(t *testing.T) {
	// Server that hangs — context should cancel before we get a response.
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Never respond.
		select {}
	}))
	defer srv.Close()

	ctx, cancel := context.WithCancel(context.Background())
	cancel() // cancel immediately

	c := kokoro.New(srv.URL)
	_, err := c.GenerateAudio(ctx, "text", "af_bella")
	if err == nil {
		t.Fatal("expected error for cancelled context")
	}
}

// ── ListVoices ────────────────────────────────────────────────────────────────

func TestListVoices_Success(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/v1/audio/voices" {
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"voices":["af_bella","am_adam","bf_emma"]}`))
			return
		}
		http.NotFound(w, r)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	voices, err := c.ListVoices(context.Background())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(voices) != 3 {
		t.Errorf("expected 3 voices, got %d: %v", len(voices), voices)
	}
	if voices[0] != "af_bella" {
		t.Errorf("expected first voice to be af_bella, got %q", voices[0])
	}
}

func TestListVoices_Non200(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusServiceUnavailable)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	_, err := c.ListVoices(context.Background())
	if err == nil {
		t.Fatal("expected error for non-200 response")
	}
	if !strings.Contains(err.Error(), "503") {
		t.Errorf("expected 503 in error, got: %v", err)
	}
}

func TestListVoices_MalformedJSON(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`not-json`))
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	_, err := c.ListVoices(context.Background())
	if err == nil {
		t.Fatal("expected error for malformed JSON")
	}
}

func TestListVoices_EmptyVoices(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"voices":[]}`))
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL)
	voices, err := c.ListVoices(context.Background())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(voices) != 0 {
		t.Errorf("expected 0 voices, got %d", len(voices))
	}
}

// ── New ───────────────────────────────────────────────────────────────────────

func TestNew_TrailingSlashStripped(t *testing.T) {
	// Verify that a trailing slash on baseURL doesn't produce double-slash paths.
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/v1/audio/voices" {
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"voices":["af_bella"]}`))
			return
		}
		http.NotFound(w, r)
	}))
	defer srv.Close()

	c := kokoro.New(srv.URL + "/") // trailing slash
	voices, err := c.ListVoices(context.Background())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(voices) == 0 {
		t.Error("expected at least one voice")
	}
}
