package tts

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"go.uber.org/zap"
)

// mockTTSServer 创建模拟 OpenAI TTS API 的 HTTP 服务器。
func mockTTSServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
	t.Helper()
	return httptest.NewServer(handler)
}

// sendSentences 向 channel 发送句子并关闭。
func sendSentences(sentences ...string) <-chan string {
	ch := make(chan string, len(sentences))
	for _, s := range sentences {
		ch <- s
	}
	close(ch)
	return ch
}

func TestOpenAIService_SynthesizeStream_Success(t *testing.T) {
	var callCount int32
	srv := mockTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
		atomic.AddInt32(&callCount, 1)

		if r.Method != http.MethodPost {
			t.Errorf("method = %s, want POST", r.Method)
		}
		if !strings.Contains(r.URL.Path, "/audio/speech") {
			t.Errorf("path = %s, should contain /audio/speech", r.URL.Path)
		}
		auth := r.Header.Get("Authorization")
		if auth != "Bearer test-key" {
			t.Errorf("Authorization = %q, want %q", auth, "Bearer test-key")
		}

		// 验证请求体
		body, _ := io.ReadAll(r.Body)
		if !strings.Contains(string(body), "tts-1") {
			t.Errorf("request body should contain model tts-1")
		}

		// 返回假 MP3 数据
		w.Header().Set("Content-Type", "audio/mpeg")
		fmt.Fprintf(w, "fake-mp3-data")
	})
	defer srv.Close()

	svc := NewOpenAIService("test-key", "tts-1", "alloy", srv.URL, 1.0, 5, 30, zap.NewNop().Sugar())

	textStream := sendSentences("你好", "世界", "！")

	ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{
		Voice: "alloy", Speed: 1.0, OutputFmt: "mp3", SampleRate: 24000,
	})
	if err != nil {
		t.Fatalf("SynthesizeStream() error: %v", err)
	}

	var chunks []Chunk
	for c := range ch {
		chunks = append(chunks, c)
	}

	// 应该有 3 个音频 chunk + 1 个 Final 标记
	if len(chunks) != 4 {
		t.Fatalf("got %d chunks, want 4", len(chunks))
	}

	// 验证前 3 个有音频数据，IsLast 为 true（每句结束）
	for i := 0; i < 3; i++ {
		if string(chunks[i].Audio) != "fake-mp3-data" {
			t.Errorf("chunk[%d].Audio = %q, want %q", i, string(chunks[i].Audio), "fake-mp3-data")
		}
		if !chunks[i].IsLast {
			t.Errorf("chunk[%d].IsLast should be true (sentence end)", i)
		}
		if chunks[i].Final {
			t.Errorf("chunk[%d].Final should be false", i)
		}
	}

	// 验证最后一个是 Final（整轮结束）
	if !chunks[3].Final {
		t.Error("last chunk should be Final")
	}
	if chunks[3].Audio != nil {
		t.Error("last chunk Audio should be nil")
	}

	// 验证调用了 3 次 API（3 个句子）
	if atomic.LoadInt32(&callCount) != 3 {
		t.Errorf("API called %d times, want 3", callCount)
	}
}

func TestOpenAIService_SynthesizeStream_APIError(t *testing.T) {
	srv := mockTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "internal error")
	})
	defer srv.Close()

	svc := NewOpenAIService("test-key", "tts-1", "alloy", srv.URL, 1.0, 5, 30, zap.NewNop().Sugar())

	textStream := sendSentences("你好")

	ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
	if err != nil {
		t.Fatalf("SynthesizeStream() error: %v", err)
	}

	// 应该只有一个 Final chunk（音频被跳过）
	var chunks []Chunk
	for c := range ch {
		chunks = append(chunks, c)
	}

	if len(chunks) != 1 {
		t.Fatalf("got %d chunks, want 1 (Final only)", len(chunks))
	}
	if !chunks[0].Final {
		t.Error("chunk should be Final")
	}
}

func TestOpenAIService_SynthesizeStream_Timeout(t *testing.T) {
	srv := mockTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(3 * time.Second)
		w.Header().Set("Content-Type", "audio/mpeg")
		fmt.Fprintf(w, "late-mp3")
	})
	defer srv.Close()

	// 1 秒超时
	svc := NewOpenAIService("test-key", "tts-1", "alloy", srv.URL, 1.0, 1, 30, zap.NewNop().Sugar())

	textStream := sendSentences("很长的句子")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	ch, err := svc.SynthesizeStream(ctx, textStream, Options{})
	if err != nil {
		t.Fatalf("SynthesizeStream() error: %v", err)
	}

	var chunks []Chunk
	for c := range ch {
		chunks = append(chunks, c)
	}

	// 超时后音频被跳过，只有 Final
	if len(chunks) != 1 {
		t.Fatalf("got %d chunks, want 1", len(chunks))
	}
	if !chunks[0].Final {
		t.Error("chunk should be Final")
	}
}

func TestOpenAIService_SynthesizeStream_EmptyText(t *testing.T) {
	var callCount int32
	srv := mockTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
		atomic.AddInt32(&callCount, 1)
		w.Header().Set("Content-Type", "audio/mpeg")
		fmt.Fprintf(w, "mp3")
	})
	defer srv.Close()

	svc := NewOpenAIService("test-key", "tts-1", "alloy", srv.URL, 1.0, 5, 30, zap.NewNop().Sugar())

	// 空句子应该被跳过
	textStream := sendSentences("", "你好", "")

	ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
	if err != nil {
		t.Fatalf("SynthesizeStream() error: %v", err)
	}

	var chunks []Chunk
	for c := range ch {
		chunks = append(chunks, c)
	}

	// 只有 "你好" 应该被合成
	if atomic.LoadInt32(&callCount) != 1 {
		t.Errorf("API called %d times, want 1", callCount)
	}

	// 1 个音频（IsLast: true）+ 1 个 Final
	if len(chunks) != 2 {
		t.Fatalf("got %d chunks, want 2", len(chunks))
	}
}

func TestOpenAIService_SynthesizeStream_ContextCancelled(t *testing.T) {
	srv := mockTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "audio/mpeg")
		fmt.Fprintf(w, "mp3")
	})
	defer srv.Close()

	svc := NewOpenAIService("test-key", "tts-1", "alloy", srv.URL, 1.0, 5, 30, zap.NewNop().Sugar())

	// 发送多个句子，但在第一个后取消
	textStream := make(chan string, 3)
	textStream <- "第一句"
	textStream <- "第二句"
	textStream <- "第三句"
	close(textStream)

	ctx, cancel := context.WithCancel(context.Background())
	// 立即取消
	cancel()

	ch, err := svc.SynthesizeStream(ctx, textStream, Options{})
	if err != nil {
		t.Fatalf("SynthesizeStream() error: %v", err)
	}

	// 消费 channel，应该很快结束
	var count int
	for range ch {
		count++
	}
	// 可能收到 0 个或 1 个 chunk，取决于时序
	t.Logf("received %d chunks after context cancel", count)
}

func TestOpenAIService_SynthesizeStream_PartialFailure(t *testing.T) {
	var callCount int32
	srv := mockTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
		n := atomic.AddInt32(&callCount, 1)
		if n == 2 {
			// 第二个句子失败
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "error")
			return
		}
		w.Header().Set("Content-Type", "audio/mpeg")
		fmt.Fprintf(w, "mp3-%d", n)
	})
	defer srv.Close()

	svc := NewOpenAIService("test-key", "tts-1", "alloy", srv.URL, 1.0, 5, 30, zap.NewNop().Sugar())

	textStream := sendSentences("第一句", "第二句", "第三句")

	ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{})
	if err != nil {
		t.Fatalf("SynthesizeStream() error: %v", err)
	}

	var chunks []Chunk
	for c := range ch {
		chunks = append(chunks, c)
	}

	// 2 个成功音频（IsLast: true）+ 1 个 Final（第二句被跳过）
	if len(chunks) != 3 {
		t.Fatalf("got %d chunks, want 3", len(chunks))
	}
	if !chunks[0].IsLast {
		t.Error("first audio chunk should be IsLast")
	}
	if !chunks[1].IsLast {
		t.Error("second audio chunk should be IsLast")
	}
	if !chunks[len(chunks)-1].Final {
		t.Error("last chunk should be Final")
	}
}

func TestOpenAIService_SynthesizeStream_CustomVoice(t *testing.T) {
	srv := mockTTSServer(t, func(w http.ResponseWriter, r *http.Request) {
		body, _ := io.ReadAll(r.Body)
		if !strings.Contains(string(body), "nova") {
			t.Errorf("request body should contain voice 'nova', got: %s", string(body))
		}
		w.Header().Set("Content-Type", "audio/mpeg")
		fmt.Fprintf(w, "mp3")
	})
	defer srv.Close()

	svc := NewOpenAIService("test-key", "tts-1", "alloy", srv.URL, 1.0, 5, 30, zap.NewNop().Sugar())

	textStream := sendSentences("你好")

	ch, err := svc.SynthesizeStream(context.Background(), textStream, Options{Voice: "nova"})
	if err != nil {
		t.Fatalf("SynthesizeStream() error: %v", err)
	}

	for range ch {
	}
}
