package ipc

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

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

// The default that matters most for a biometric: on a core that was never
// configured with a speaker block, there is no wire path that takes a
// voiceprint, and none that lists the ones that might exist.
func TestSpeaker_OffUnlessConfigured(t *testing.T) {
	_, _, cli, _ := newServerWithStore(t)
	ctx := context.Background()

	if _, err := cli.EnrollSpeaker(ctx, EnrollSpeakerReq{ID: "kami"}); !errors.Is(err, ErrUnknownMethod) {
		t.Errorf("EnrollSpeaker error = %v, want ErrUnknownMethod", err)
	}
	if _, err := cli.ListSpeakers(ctx); !errors.Is(err, ErrUnknownMethod) {
		t.Errorf("ListSpeakers error = %v, want ErrUnknownMethod", err)
	}
	if err := cli.ForgetSpeaker(ctx, "kami"); !errors.Is(err, ErrUnknownMethod) {
		t.Errorf("ForgetSpeaker error = %v, want ErrUnknownMethod", err)
	}
}

// Enrolment carries several samples across the boundary byte for byte — a
// profile averaged over the wrong bytes is a profile of nobody.
func TestSpeaker_EnrollCrossesTheWire(t *testing.T) {
	_, srv, cli, _ := newServerWithStore(t)
	ctx := context.Background()

	enrolled := time.Now().UTC().Truncate(time.Second)
	var gotID, gotName string
	var gotSamples [][]byte

	srv.EnrollSpeakerFn = func(_ context.Context, req EnrollSpeakerReq) (EnrollSpeakerResp, error) {
		gotID, gotName = req.ID, req.Name
		for _, s := range req.Samples {
			gotSamples = append(gotSamples, s.Bytes)
		}
		return EnrollSpeakerResp{Speaker: Speaker{
			ID: req.ID, Name: req.Name, Enrolled: enrolled, Samples: len(req.Samples),
		}}, nil
	}

	mk := func(b byte, n int) audio.Audio {
		buf := make([]byte, n)
		for i := range buf {
			buf[i] = b
		}
		return audio.Audio{Format: audio.PCM16kMono, Bytes: buf}
	}
	samples := []audio.Audio{mk(1, 64), mk(2, 96), mk(3, 128)}

	resp, err := cli.EnrollSpeaker(ctx, EnrollSpeakerReq{ID: "kami", Name: "Ками", Samples: samples})
	if err != nil {
		t.Fatalf("EnrollSpeaker: %v", err)
	}
	if gotID != "kami" || gotName != "Ками" {
		t.Errorf("server saw id=%q name=%q", gotID, gotName)
	}
	if len(gotSamples) != 3 {
		t.Fatalf("server saw %d samples, want 3", len(gotSamples))
	}
	for i, want := range samples {
		if string(gotSamples[i]) != string(want.Bytes) {
			t.Errorf("sample %d altered in transit", i)
		}
	}
	if resp.Speaker.Samples != 3 || !resp.Speaker.Enrolled.Equal(enrolled) {
		t.Errorf("profile came back wrong: %+v", resp.Speaker)
	}
}

// A listing says who is enrolled and whether recognition actually works. On
// this box the honest answer is "enrolled, not recognising", and the response
// has to be able to say so — otherwise a surface implies Maven knows who is
// talking when nothing on disk can tell.
func TestSpeaker_ListReportsDisabledRecognition(t *testing.T) {
	_, srv, cli, _ := newServerWithStore(t)
	ctx := context.Background()

	srv.ListSpeakersFn = func(context.Context) (ListSpeakersResp, error) {
		return ListSpeakersResp{
			Speakers: []Speaker{{ID: "kami", Name: "Ками", Samples: 3}},
			Enabled:  false,
		}, nil
	}

	resp, err := cli.ListSpeakers(ctx)
	if err != nil {
		t.Fatalf("ListSpeakers: %v", err)
	}
	if len(resp.Speakers) != 1 || resp.Speakers[0].ID != "kami" {
		t.Fatalf("speakers = %+v", resp.Speakers)
	}
	if resp.Enabled {
		t.Error("Enabled = true; the seam must be able to report that nothing recognises")
	}
}

// Deletion reaches core with the id intact and reports success. This is the
// request that must always work.
func TestSpeaker_ForgetReachesCore(t *testing.T) {
	_, srv, cli, _ := newServerWithStore(t)
	ctx := context.Background()

	var forgot string
	srv.ForgetSpeakerFn = func(_ context.Context, req ForgetSpeakerReq) error {
		forgot = req.ID
		return nil
	}
	if err := cli.ForgetSpeaker(ctx, "гость"); err != nil {
		t.Fatalf("ForgetSpeaker: %v", err)
	}
	if forgot != "гость" {
		t.Errorf("core forgot %q, want %q", forgot, "гость")
	}
}
