package main

import (
	"context"
	"io"
	"log/slog"
	"testing"
	"time"
)

// newTestBot builds a Bot with just the fields the telemetry path needs — no network,
// so it sidesteps NewBot's identity check.
func newTestBot(st *Store, cfg *Config) *Bot {
	return &Bot{cfg: cfg, st: st, log: slog.New(slog.NewTextHandler(io.Discard, nil)), promptVersion: "testv"}
}

func requestLogCount(t *testing.T, st *Store) int {
	t.Helper()
	ctx, cancel := opContext()
	defer cancel()
	var n int
	if err := st.pool.QueryRow(ctx, `SELECT count(*) FROM request_log`).Scan(&n); err != nil {
		t.Fatalf("count: %v", err)
	}
	return n
}

// TestRecordSkipWritesRow proves the early-return telemetry path actually records a
// row (route=none + the skip reason) when TELEMETRY_ENABLED is on. The write is async,
// so poll briefly.
func TestRecordSkipWritesRow(t *testing.T) {
	st := openTestStore(t)
	defer st.Close()
	b := newTestBot(st, &Config{TelemetryEnabled: true})

	ev := &Event{EventID: "$skip-1", RoomID: "!r:vojo.chat", Sender: "@u:vojo.chat"}
	b.recordSkip(context.Background(), ev, degradeMedia)

	deadline := time.Now().Add(2 * time.Second)
	for requestLogCount(t, st) == 0 && time.Now().Before(deadline) {
		time.Sleep(20 * time.Millisecond)
	}
	if n := requestLogCount(t, st); n != 1 {
		t.Fatalf("telemetry rows = %d, want 1", n)
	}
	ctx, cancel := opContext()
	defer cancel()
	var route, degraded string
	if err := st.pool.QueryRow(ctx,
		`SELECT route, degraded FROM request_log WHERE id = $1`, ev.EventID).Scan(&route, &degraded); err != nil {
		t.Fatalf("read: %v", err)
	}
	if route != routeNone || degraded != degradeMedia {
		t.Fatalf("row = (%q,%q), want (none, media)", route, degraded)
	}
}

// TestTelemetryStripsTextWhenStoreTextOff proves the content gate: with TELEMETRY_ENABLED
// on but TELEMETRY_STORE_TEXT off, the user query, the model-authored search query, and the
// answer are all NULL — only metadata signals land. The boolean signals are still recorded.
func TestTelemetryStripsTextWhenStoreTextOff(t *testing.T) {
	st := openTestStore(t)
	defer st.Close()
	b := newTestBot(st, &Config{TelemetryEnabled: true, TelemetryStoreText: false})

	b.recordTelemetry(context.Background(), RequestLog{
		ID: "$strip-1", Route: routeWebThenGrok, RouterSource: "classifier",
		QueryText: "secret query", SearchQuery: "secret search", AnswerText: "secret answer",
		NeedsWeb: true, WebDecidedBy: "classifier_needs_web", OK: true,
	})

	deadline := time.Now().Add(2 * time.Second)
	for requestLogCount(t, st) == 0 && time.Now().Before(deadline) {
		time.Sleep(20 * time.Millisecond)
	}
	ctx, cancel := opContext()
	defer cancel()
	var qt, sq, ans, decidedBy *string
	var needsWeb bool
	if err := st.pool.QueryRow(ctx,
		`SELECT query_text, search_query, answer_text, web_decided_by, needs_web FROM request_log WHERE id=$1`,
		"$strip-1").Scan(&qt, &sq, &ans, &decidedBy, &needsWeb); err != nil {
		t.Fatalf("read: %v", err)
	}
	if qt != nil || sq != nil || ans != nil {
		t.Fatalf("text columns must be NULL when store-text off: qt=%v sq=%v ans=%v", qt, sq, ans)
	}
	// Metadata is still recorded (it is not content).
	if !needsWeb || decidedBy == nil || *decidedBy != "classifier_needs_web" {
		t.Fatalf("metadata signals must survive: needsWeb=%v decidedBy=%v", needsWeb, decidedBy)
	}
}

// TestTelemetryDisabledWritesNothing proves the default (TELEMETRY_ENABLED off) adds
// no write path — strict "cascade-off == today".
func TestTelemetryDisabledWritesNothing(t *testing.T) {
	st := openTestStore(t)
	defer st.Close()
	b := newTestBot(st, &Config{TelemetryEnabled: false})

	b.recordSkip(context.Background(), &Event{EventID: "$skip-2", RoomID: "!r:vojo.chat", Sender: "@u:vojo.chat"}, degradeMedia)

	// Give any (incorrect) async write time to land, then assert nothing was written.
	time.Sleep(200 * time.Millisecond)
	if n := requestLogCount(t, st); n != 0 {
		t.Fatalf("telemetry rows = %d, want 0 (TELEMETRY_ENABLED off)", n)
	}
}
