package main

import (
	"bytes"
	"context"
	"path/filepath"
	"testing"

	"github.com/kami/maven/internal/audio"
	"github.com/kami/maven/internal/tts"
	"github.com/kami/maven/internal/worker"
)

func TestStubHandlerSynthesize_ReturnsPCM16kMono(t *testing.T) {
	h := &stubHandler{}
	resp, err := h.Synthesize(context.Background(), worker.SynthesizeReq{Text: "hello"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.Audio.Format != audio.PCM16kMono {
		t.Errorf("format = %v, want %v", resp.Audio.Format, audio.PCM16kMono)
	}
}

func TestStubHandlerSynthesize_NonEmptyAudio(t *testing.T) {
	h := &stubHandler{}
	resp, err := h.Synthesize(context.Background(), worker.SynthesizeReq{Text: "hello"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(resp.Audio.Bytes) == 0 {
		t.Error("audio bytes are empty")
	}
	if len(resp.Audio.Bytes) != 6400 {
		t.Logf("audio bytes = %d (expected ~6400 for 200ms@16kHz)", len(resp.Audio.Bytes))
	}
}

func TestStubHandlerSynthesize_DifferentTextDifferentAudio(t *testing.T) {
	h := &stubHandler{}
	a, _ := h.Synthesize(context.Background(), worker.SynthesizeReq{Text: "hello"})
	b, _ := h.Synthesize(context.Background(), worker.SynthesizeReq{Text: "world"})
	if bytes.Equal(a.Audio.Bytes, b.Audio.Bytes) {
		t.Error("different text produced identical audio")
	}
}

func TestStubHandlerSynthesize_EmptyTextProducesAudio(t *testing.T) {
	h := &stubHandler{}
	resp, err := h.Synthesize(context.Background(), worker.SynthesizeReq{Text: ""})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(resp.Audio.Bytes) == 0 {
		t.Error("empty text produced no audio")
	}
}

func TestResample22050To16000_EmptyInput(t *testing.T) {
	out := resample22050To16000(nil)
	if len(out) != 0 {
		t.Errorf("expected empty output, got %d bytes", len(out))
	}
}

func TestResample22050To16000_ProducesExpectedLength(t *testing.T) {
	input := make([]byte, 44100)
	for i := range input {
		input[i] = byte(i)
	}
	out := resample22050To16000(input)
	expected := len(input) * 16000 / 22050
	if diff := abs(len(out) - expected); diff > 4 {
		t.Errorf("output length = %d, want ~%d (diff %d)", len(out), expected, diff)
	}
}

func TestDefaultSocket_WithXdgRuntimeDir(t *testing.T) {
	t.Setenv("XDG_RUNTIME_DIR", "/run/user/1000")
	path := defaultSocket("tts.sock")
	want := "/run/user/1000/maven/tts.sock"
	if path != want {
		t.Errorf("got %q, want %q", path, want)
	}
}

func TestSynthesizeRoundTripViaSocket(t *testing.T) {
	sockDir := t.TempDir()
	sockPath := filepath.Join(sockDir, "tts.sock")

	h := &stubHandler{}
	srv := worker.NewSynthesizerServer(sockPath, h)
	if err := srv.Listen(); err != nil {
		t.Fatal(err)
	}
	go srv.Serve()
	defer srv.Close()

	client := worker.Dial(sockPath)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Synthesize(ctx, worker.SynthesizeReq{Text: "test"})
	if err != nil {
		t.Fatalf("synthesize: %v", err)
	}
	if resp.Audio.Format != audio.PCM16kMono {
		t.Errorf("format = %v, want %v", resp.Audio.Format, audio.PCM16kMono)
	}
	if len(resp.Audio.Bytes) == 0 {
		t.Error("audio bytes are empty")
	}
}

func abs(n int) int {
	if n < 0 {
		return -n
	}
	return n
}

// The dictionary that ships in deploy/ must parse and must be non-empty. It is
// data, so nothing else would catch a trailing comma before the voice did.
func TestShippedLexiconLoads(t *testing.T) {
	lex, err := tts.LoadLexicon(filepath.Join("..", "..", "deploy", "tts-lexicon.json"))
	if err != nil {
		t.Fatalf("deploy/tts-lexicon.json: %v", err)
	}
	if lex.Size() < 10 {
		t.Errorf("shipped dictionary holds %d names, want the full list", lex.Size())
	}
	if got := lex.Apply("задача в Vikunja"); got == "задача в Vikunja" {
		t.Error("the shipped dictionary did not rewrite a name it lists")
	}
}
