// piper.go Piper HTTP sunucusuyla konuşur (yerel, gizlilik korumalı, ücretsiz).
//
// Piper'ın Python HTTP sunucusu POST /api/tts ucunda {text, voice} JSON'u bekler ve
// ham WAV döner. Farkı OpenAI uyumundan iki yerde toplanır: gövde biçimi ve
// Content-Type — bu yüzden ayrı uygulama olarak yazıldı.
package tts

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// Piper, Piper HTTP sunucusu uygulamasıdır.
type Piper struct {
	url    string
	voice  string
	speed  float64
	client *http.Client
}

// NewPiper tanım oluşturur. URL boşsa nil döner.
func NewPiper(opt Options) *Piper {
	url := strings.TrimSpace(opt.URL)
	if url == "" {
		return nil
	}
	timeout := opt.Timeout
	if timeout <= 0 {
		timeout = DefaultTimeout
	}
	return &Piper{
		url:    url,
		voice:  strings.TrimSpace(opt.Voice),
		speed:  opt.Speed,
		client: &http.Client{Timeout: timeout},
	}
}

// Speak metni sese çevirir.
func (p *Piper) Speak(ctx context.Context, text string) (Speech, error) {
	if p == nil {
		return Speech{}, ErrNoSynthesizer
	}
	text = Plain(text)
	if text == "" {
		return Speech{}, ErrEmptyText
	}

	// Piper konuşma hızını uzunluk ölçüsüyle ister: 1.0 normal, 2.0 iki kat yavaş.
	req := map[string]any{"text": text}
	if p.voice != "" {
		req["voice"] = p.voice
	}
	if p.speed > 0 {
		req["length_scale"] = p.speed
	}
	body, err := json.Marshal(req)
	if err != nil {
		return Speech{}, err
	}

	// Uç adı ve adres biçimi SÜRÜME GÖRE değişiyor: Piper 1.8+ "/synthesize",
	// 1.2 "/api/tts". Kullanıcı Taban adresi verdiğimizi varsayıyoruz ve ikisini
	// de deniyoruz; 404 dışındaki hatalarda (401/500 vb) tekrar denemiyoruz.
	base := strings.TrimRight(p.url, "/")
	for _, yol := range []string{base + "/synthesize", base + "/api/tts", base} {
		audio, err := p.post(ctx, yol, body)
		if err == nil {
			return Speech{Audio: audio, Mime: "audio/wav"}, nil
		}
		var perr *piperHTTPError
		if !errors.As(err, &perr) || perr.status != http.StatusNotFound {
			return Speech{}, err // 404 değilse tekrar denemenin anlamı yok
		}
	}
	return Speech{}, fmt.Errorf("%w: uç bulunamadı (%s)", ErrUnavailable, base)
}

// piperHTTPError, HTTP durum kodunu taşır (geri düşüş kararı için gerekli).
type piperHTTPError struct{ status int }

func (e *piperHTTPError) Error() string { return fmt.Sprintf("piper: HTTP %d", e.status) }

// post, Piper'a isteği gönderip ham ses baytlarını döner.
func (p *Piper) post(ctx context.Context, url string, body []byte) ([]byte, error) {
	hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	hreq.Header.Set("Content-Type", "application/json")

	resp, err := p.client.Do(hreq)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", ErrUnavailable, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		raw, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
		if resp.StatusCode == http.StatusNotFound {
			return nil, &piperHTTPError{status: resp.StatusCode}
		}
		return nil, fmt.Errorf("%w: HTTP %d: %s", ErrUnavailable, resp.StatusCode, strings.TrimSpace(string(raw)))
	}
	audio, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("%w: gövde okunamadı: %v", ErrUnavailable, err)
	}
	if len(audio) == 0 {
		return nil, fmt.Errorf("%w: boş ses", ErrUnavailable)
	}
	return audio, nil
}
