package tts

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

func TestPlainMarkdown(t *testing.T) {
	cases := []struct{ name, in, want string }{
		{"kalın", "**önemli** bir şey", "önemli bir şey."},
		{"başlık", "## Nasıl yapılır?", "Nasıl yapılır?"},
		{"madde", "- birinci\n- ikinci", "birinci. ikinci."},
		{"numaralı", "1. birinci\n2. ikinci", "birinci. ikinci."},
		// DİKKAT: bağlantıdan ÖNCEki metin korunur (regresyon: "bkz. " kayboluyordu).
		{"bağlantı", "bkz. [doküman](http://x/y) dosyası", "bkz. doküman dosyası."},
		{"yatay çizgi", "önce\n---\nsonra", "önce. sonra."},
		{"kod bloğu atlanır", "Merhaba.\n```go\nfmt.Println(1)\n```\nGörüşürüz.", "Merhaba. Görüşürüz."},
		{"yalnız kod", "```js\nconsole.log(1)\n```", ""},
		{"satır sonu cümledir", "birinci satır\nikinci satır", "birinci satır. ikinci satır."},
		{"Türkçe karakterler", "Merhaba, nasılsınız? İyiyim.", "Merhaba, nasılsınız? İyiyim."},
		{"çift nokta olmaz", "Merhaba.\n```x\n1\n```\nGörüşürüz.", "Merhaba. Görüşürüz."},
		{"tek yıldız korunur", "sonuç 2 * 3 = 6", "sonuç 2 * 3 = 6."},
		{"unicode üç nokta", "Biliyorum… görüşürüz", "Biliyorum... görüşürüz."},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := Plain(c.in); got != c.want {
				t.Fatalf("Plain(%q) = %q, beklenen %q", c.in, got, c.want)
			}
		})
	}
}

func TestSplitSentences(t *testing.T) {
	got := Split("Bir. İki! Üç? Dört. Beş", 0)
	// SON cümlede de nokta vardır: okuma için her cümle nokta ile bitmelidir.
	want := []string{"Bir.", "İki!", "Üç?", "Dört.", "Beş."}
	if len(got) != len(want) {
		t.Fatalf("cümle sayısı = %d (%v), beklenen %d", len(got), got, len(want))
	}
	for i := range want {
		if got[i] != want[i] {
			t.Errorf("%d. cümle = %q, beklenen %q", i, got[i], want[i])
		}
	}
}

func TestSplitNoktaSayisiniBolmez(t *testing.T) {
	// Sürüm 3.14 ve alan adı cümle sonu DEĞİLDİR.
	got := Split("Sürüm 3.14 çalışıyor. Site fiko-ai.com.tr yayında.", 0)
	if len(got) != 2 {
		t.Fatalf("cümle sayısı = %d (%v), beklenen 2", len(got), got)
	}
	if !strings.Contains(got[0], "3.14") {
		t.Errorf("ilk cümle 3.14 içermeli: %q", got[0])
	}
	if !strings.Contains(got[1], "fiko-ai.com.tr") {
		t.Errorf("ikinci cümle alan adını içermeli: %q", got[1])
	}
}

func TestSplitUzunCumleyiKirar(t *testing.T) {
	uzun := strings.Repeat("kelime ", 200) + "son."
	parcalar := Split(uzun, 100)
	if len(parcalar) < 2 {
		t.Fatalf("uzun cümle bölünmedi: %d parça", len(parcalar))
	}
	for i, p := range parcalar {
		if len([]rune(p)) > 100 {
			t.Errorf("%d. parça sınırı aştı (%d karakter): %q", i, len([]rune(p)), p)
		}
	}
	// Sıralama ve kayıp yok: parçalar birleşince kelimeler korunur.
	if !strings.HasSuffix(parcalar[len(parcalar)-1], "son.") {
		t.Errorf("son parça cümlenin sonuyla bitmeli: %q", parcalar[len(parcalar)-1])
	}
}

func TestSplitBosMetin(t *testing.T) {
	if got := Split("", 100); got != nil {
		t.Errorf("boş metin nil dönmeli, %v geldi", got)
	}
	if got := Split("```\nkod\n```", 100); got != nil {
		t.Errorf("yalnız kod bloğu nil dönmeli, %v geldi", got)
	}
}

// ---- motor uygulamaları ----

func TestOpenAISpeak(t *testing.T) {
	var gotBody map[string]any
	var gotAuth string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotAuth = r.Header.Get("Authorization")
		_ = json.NewDecoder(r.Body).Decode(&gotBody)
		w.Write([]byte("SAHTE-SES-BAYTLARI"))
	}))
	defer srv.Close()

	s := NewOpenAI(Options{URL: srv.URL, APIKey: "gizli", Voice: "nova", Speed: 1.2, Model: "tts-1"})
	sp, err := s.Speak(context.Background(), "**Merhaba** dünya.")
	if err != nil {
		t.Fatalf("Speak hatası: %v", err)
	}
	if string(sp.Audio) != "SAHTE-SES-BAYTLARI" {
		t.Errorf("ses gövdesi yanlış: %q", sp.Audio)
	}
	if sp.Mime != "audio/mpeg" {
		t.Errorf("mime = %q, beklenen audio/mpeg", sp.Mime)
	}
	if gotAuth != "Bearer gizli" {
		t.Errorf("Authorization = %q, beklenen 'Bearer gizli'", gotAuth)
	}
	if gotBody["input"] != "Merhaba dünya." {
		t.Errorf("input = %v, beklenen markdown temizlenmiş metin", gotBody["input"])
	}
	if gotBody["voice"] != "nova" || gotBody["model"] != "tts-1" || gotBody["response_format"] != "mp3" {
		t.Errorf("istek gövdesi eksik: %v", gotBody)
	}
	if gotBody["speed"] != 1.2 {
		t.Errorf("speed = %v, beklenen 1.2", gotBody["speed"])
	}
}

func TestOpenAIHatalar(t *testing.T) {
	if NewOpenAI(Options{}) != nil {
		t.Error("URL boşsa nil dönmeli")
	}
	s := NewOpenAI(Options{URL: "http://127.0.0.1:1/x"})
	if _, err := s.Speak(context.Background(), ""); !isErr(err, ErrEmptyText) {
		t.Errorf("boş metin hatası = %v, beklenen ErrEmptyText", err)
	}
	if _, err := s.Speak(context.Background(), "metin"); !isErr(err, ErrUnavailable) {
		t.Errorf("kapalı motor hatası = %v, beklenen ErrUnavailable", err)
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = io.WriteString(w, "motor çöktü")
	}))
	defer srv.Close()
	s2 := NewOpenAI(Options{URL: srv.URL})
	if _, err := s2.Speak(context.Background(), "merhaba"); !isErr(err, ErrUnavailable) {
		t.Errorf("HTTP 500 hatası = %v, beklenen ErrUnavailable", err)
	}
}

func TestPiperSpeak(t *testing.T) {
	var gotBody map[string]any
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_ = json.NewDecoder(r.Body).Decode(&gotBody)
		_, _ = w.Write([]byte("RIFF....WAVE"))
	}))
	defer srv.Close()

	s := NewPiper(Options{URL: srv.URL, Voice: "tr_TR-female", Speed: 1.1})
	sp, err := s.Speak(context.Background(), "Merhaba.")
	if err != nil {
		t.Fatalf("Speak hatası: %v", err)
	}
	if sp.Mime != "audio/wav" {
		t.Errorf("mime = %q, beklenen audio/wav", sp.Mime)
	}
	if gotBody["text"] != "Merhaba." {
		t.Errorf("text = %v", gotBody["text"])
	}
	if gotBody["voice"] != "tr_TR-female" {
		t.Errorf("voice = %v", gotBody["voice"])
	}
	if gotBody["length_scale"] != 1.1 {
		t.Errorf("length_scale = %v, beklenen 1.1", gotBody["length_scale"])
	}
}

func TestPiperBosURL(t *testing.T) {
	if NewPiper(Options{}) != nil {
		t.Error("URL boşsa nil dönmeli")
	}
}

// TestPiper404Dusus: yeni uc (/synthesize) 404 verirse eski uc (/api/tts) denenir.
//
// Piper 1.8 "/synthesize", 1.2 "/api/tts" kullanıyor; kullanici TABAN adresi verir.
func TestPiper404Dusus(t *testing.T) {
	var yollar []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		yollar = append(yollar, r.URL.Path)
		if r.URL.Path == "/api/tts" {
			_, _ = w.Write([]byte("RIFFYENISESI"))
			return
		}
		w.WriteHeader(http.StatusNotFound)
	}))
	defer srv.Close()

	s := NewPiper(Options{URL: srv.URL})
	sp, err := s.Speak(context.Background(), "merhaba")
	if err != nil {
		t.Fatalf("Speak hatası (geri düşüş çalışmadı): %v", err)
	}
	if string(sp.Audio) != "RIFFYENISESI" {
		t.Errorf("ses = %q, beklenen RIFFYENISESI", sp.Audio)
	}
	if len(yollar) != 2 || yollar[0] != "/synthesize" || yollar[1] != "/api/tts" {
		t.Errorf("istenen yollar = %v, beklenen [/synthesize /api/tts]", yollar)
	}
}

// Yeni sürümde ilk deneme (/synthesize) tutmalı — gereksiz istek atılmamalı.
func TestPiperYeniUcTercihEdilir(t *testing.T) {
	var yollar []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		yollar = append(yollar, r.URL.Path)
		_, _ = w.Write([]byte("RIFF"))
	}))
	defer srv.Close()

	s := NewPiper(Options{URL: srv.URL})
	if _, err := s.Speak(context.Background(), "merhaba"); err != nil {
		t.Fatalf("Speak hatası: %v", err)
	}
	if len(yollar) != 1 || yollar[0] != "/synthesize" {
		t.Errorf("istenen yollar = %v, beklenen yalnız [/synthesize]", yollar)
	}
}

// Hiçbir uç yoksa hata döner (sessizce boş ses üretilmez).
func TestPiperHicbirUcYok(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusNotFound)
	}))
	defer srv.Close()

	s := NewPiper(Options{URL: srv.URL})
	if _, err := s.Speak(context.Background(), "merhaba"); !isErr(err, ErrUnavailable) {
		t.Errorf("hata = %v, beklenen ErrUnavailable", err)
	}
}

// 404 dışındaki hata (500) tekrar denenmez — kullanıcı gerçek hatayı görsün.
func TestPiper500TekrarDenenmez(t *testing.T) {
	var sayac int
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		sayac++
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer srv.Close()

	s := NewPiper(Options{URL: srv.URL})
	if _, err := s.Speak(context.Background(), "merhaba"); !isErr(err, ErrUnavailable) {
		t.Errorf("hata = %v, beklenen ErrUnavailable", err)
	}
	if sayac != 1 {
		t.Errorf("istek sayısı = %d, beklenen 1 (500'de tekrar denenmemeli)", sayac)
	}
}

func isErr(err, target error) bool {
	return err != nil && strings.Contains(err.Error(), target.Error())
}
