package ipc

import (
	"context"
	"errors"
	"testing"
	"time"

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

// The load-bearing default for the most invasive capability Maven has: on a core
// that was never configured to record, there is no wire path that starts a
// recording, feeds one, or harvests one. Every one of the four methods refuses.
func TestCapture_OffUnlessConfigured(t *testing.T) {
	_, _, cli, _ := newServerWithStore(t)
	ctx := context.Background()

	if _, err := cli.CaptureStart(ctx, CaptureStartReq{Label: "встреча"}); !errors.Is(err, ErrUnknownMethod) {
		t.Errorf("CaptureStart error = %v, want ErrUnknownMethod", err)
	}
	if _, err := cli.CaptureAppend(ctx, CaptureAppendReq{}); !errors.Is(err, ErrUnknownMethod) {
		t.Errorf("CaptureAppend error = %v, want ErrUnknownMethod", err)
	}
	if _, err := cli.CaptureStop(ctx, CaptureStopReq{}); !errors.Is(err, ErrUnknownMethod) {
		t.Errorf("CaptureStop error = %v, want ErrUnknownMethod", err)
	}
	if _, err := cli.CaptureStatus(ctx); !errors.Is(err, ErrUnknownMethod) {
		t.Errorf("CaptureStatus error = %v, want ErrUnknownMethod", err)
	}
}

// With the hooks wired, a whole session crosses the boundary intact: the label
// out, the audio in, the summary back.
func TestCapture_RoundTrip(t *testing.T) {
	_, srv, cli, _ := newServerWithStore(t)
	ctx := context.Background()

	started := time.Now().UTC().Truncate(time.Second)
	var gotLabel string
	var gotBytes int
	var gotDiscard bool

	srv.CaptureStartFn = func(_ context.Context, req CaptureStartReq) (CaptureStartResp, error) {
		gotLabel = req.Label
		return CaptureStartResp{Label: req.Label, Started: started, MaxSeconds: 7200}, nil
	}
	srv.CaptureAppendFn = func(_ context.Context, req CaptureAppendReq) (CaptureAppendResp, error) {
		gotBytes = len(req.Audio.Bytes)
		return CaptureAppendResp{Seconds: 1.5}, nil
	}
	srv.CaptureStopFn = func(_ context.Context, req CaptureStopReq) (CaptureStopResp, error) {
		gotDiscard = req.Discard
		return CaptureStopResp{BlobID: "abc", Summary: "— решили купить насос", Chunks: 1}, nil
	}
	srv.CaptureStatusFn = func(context.Context) (CaptureStatusResp, error) {
		return CaptureStatusResp{Running: true, Label: "встреча", Seconds: 1.5}, nil
	}

	start, err := cli.CaptureStart(ctx, CaptureStartReq{Label: "встреча с подрядчиком"})
	if err != nil {
		t.Fatalf("CaptureStart: %v", err)
	}
	if gotLabel != "встреча с подрядчиком" || start.MaxSeconds != 7200 {
		t.Errorf("start = %+v (label seen: %q)", start, gotLabel)
	}
	if !start.Started.Equal(started) {
		t.Errorf("started = %v, want %v", start.Started, started)
	}

	// Audio must survive the JSON round trip byte for byte — a base64 mistake
	// here would be silence in the transcript, not a visible error.
	pcm := []byte{1, 2, 3, 4, 5, 6, 7, 8}
	ap, err := cli.CaptureAppend(ctx, CaptureAppendReq{
		Audio: audio.Audio{Format: audio.PCM16kMono, Bytes: pcm},
	})
	if err != nil {
		t.Fatalf("CaptureAppend: %v", err)
	}
	if gotBytes != len(pcm) {
		t.Errorf("%d bytes arrived, sent %d", gotBytes, len(pcm))
	}
	if ap.Seconds != 1.5 || ap.Expired {
		t.Errorf("append resp = %+v", ap)
	}

	st, err := cli.CaptureStatus(ctx)
	if err != nil {
		t.Fatalf("CaptureStatus: %v", err)
	}
	if !st.Running || st.Label != "встреча" {
		t.Errorf("status = %+v", st)
	}

	stop, err := cli.CaptureStop(ctx, CaptureStopReq{})
	if err != nil {
		t.Fatalf("CaptureStop: %v", err)
	}
	if gotDiscard {
		t.Error("a plain stop arrived as a discard")
	}
	if stop.BlobID != "abc" || stop.Summary == "" {
		t.Errorf("stop = %+v", stop)
	}
}

// "забудь, не записывай" has to reach core as a discard, not as an ordinary
// stop that quietly keeps everything.
func TestCapture_DiscardCrossesTheWire(t *testing.T) {
	_, srv, cli, _ := newServerWithStore(t)
	var gotDiscard bool
	srv.CaptureStopFn = func(_ context.Context, req CaptureStopReq) (CaptureStopResp, error) {
		gotDiscard = req.Discard
		return CaptureStopResp{Discarded: req.Discard}, nil
	}
	resp, err := cli.CaptureStop(context.Background(), CaptureStopReq{Discard: true})
	if err != nil {
		t.Fatalf("CaptureStop: %v", err)
	}
	if !gotDiscard || !resp.Discarded {
		t.Errorf("discard lost: sent true, core saw %v, resp %+v", gotDiscard, resp)
	}
}
