package main

import (
	"context"
	"io"
	"log/slog"
	"strings"
	"sync"
	"testing"
	"time"

	"reasonix/internal/bot"
	"reasonix/internal/event"
)

type desktopForwardTestAdapter struct {
	platform bot.Platform
	name     string
	messages chan bot.InboundMessage
	entered  chan struct{}
	release  chan struct{}
	sent     chan bot.OutboundMessage
	once     sync.Once
}

func newDesktopForwardTestAdapter() *desktopForwardTestAdapter {
	return &desktopForwardTestAdapter{
		platform: bot.PlatformFeishu,
		name:     "forward-test",
		messages: make(chan bot.InboundMessage),
		entered:  make(chan struct{}),
		release:  make(chan struct{}),
		sent:     make(chan bot.OutboundMessage, 8),
	}
}

func (a *desktopForwardTestAdapter) Platform() bot.Platform { return a.platform }
func (a *desktopForwardTestAdapter) Name() string           { return a.name }
func (a *desktopForwardTestAdapter) Start(context.Context) error {
	return nil
}
func (a *desktopForwardTestAdapter) Stop() error { return nil }
func (a *desktopForwardTestAdapter) SendTyping(context.Context, string) error {
	return nil
}
func (a *desktopForwardTestAdapter) Messages() <-chan bot.InboundMessage {
	return a.messages
}
func (a *desktopForwardTestAdapter) Send(ctx context.Context, msg bot.OutboundMessage) (bot.SendResult, error) {
	a.once.Do(func() { close(a.entered) })
	select {
	case <-a.release:
	case <-ctx.Done():
		return bot.SendResult{}, ctx.Err()
	}
	a.sent <- msg
	return bot.SendResult{MessageID: "sent"}, nil
}

func newDesktopForwardTestRuntime(adapter bot.Adapter) *desktopBotRuntime {
	gw := bot.NewGatewayWithAdapterBindings(bot.GatewayConfig{}, []bot.AdapterBinding{{
		ID:       "feishu-lark",
		Domain:   "lark",
		Platform: bot.PlatformFeishu,
		Adapter:  adapter,
	}}, slog.New(slog.NewTextHandler(io.Discard, nil)))
	return &desktopBotRuntime{gw: gw}
}

func TestBotEventForwarderDoesNotBlockEventEmissionOnSlowSend(t *testing.T) {
	adapter := newDesktopForwardTestAdapter()
	forwarder := newBotEventForwarder(newDesktopForwardTestRuntime(adapter), []botForwardTarget{{
		ConnID:   "feishu-lark",
		Domain:   "lark",
		ChatID:   "oc-group-1",
		ChatType: bot.ChatGroup,
	}})

	done := make(chan struct{})
	go func() {
		forwarder.Emit(event.Event{Kind: event.Text, Text: strings.Repeat("x", 400)})
		close(done)
	}()

	select {
	case <-done:
	case <-time.After(500 * time.Millisecond):
		t.Fatal("Emit blocked behind slow bot send")
	}
	select {
	case <-adapter.entered:
	case <-time.After(500 * time.Millisecond):
		t.Fatal("adapter send did not start")
	}

	forwarder.Close()
	close(adapter.release)
	select {
	case <-adapter.sent:
	case <-time.After(500 * time.Millisecond):
		t.Fatal("queued bot message was not sent after adapter release")
	}
}

func TestBotEventForwarderApprovalNoticeDoesNotExposeReplyID(t *testing.T) {
	adapter := newDesktopForwardTestAdapter()
	close(adapter.release)
	forwarder := newBotEventForwarder(newDesktopForwardTestRuntime(adapter), []botForwardTarget{{
		ConnID:   "feishu-lark",
		Domain:   "lark",
		ChatID:   "oc-group-1",
		ChatType: bot.ChatGroup,
	}})

	forwarder.Emit(event.Event{Kind: event.ApprovalRequest, Approval: event.Approval{
		ID:      "approval-1",
		Tool:    "shell",
		Subject: "run command",
	}})
	forwarder.Close()

	select {
	case msg := <-adapter.sent:
		if strings.Contains(msg.Text, "approval-1") || strings.Contains(msg.Text, "/approve") {
			t.Fatalf("approval notice exposed unusable reply routing: %q", msg.Text)
		}
		if !strings.Contains(msg.Text, "桌面") {
			t.Fatalf("approval notice = %q, want desktop guidance", msg.Text)
		}
	case <-time.After(500 * time.Millisecond):
		t.Fatal("approval notice was not sent")
	}
}

func TestBotEventForwarderSuppressesOperatorNoticesWithoutHidingUserWarnings(t *testing.T) {
	forwarder := &botEventForwarder{
		runtime: &desktopBotRuntime{},
		targets: []botForwardTarget{{
			ConnID:   "feishu-lark",
			Domain:   "lark",
			ChatID:   "oc-group-1",
			ChatType: bot.ChatGroup,
		}},
		queue: make(chan string, 2),
	}

	forwarder.Emit(event.Event{
		Kind:     event.Notice,
		Level:    event.LevelWarn,
		Audience: event.NoticeAudienceOperator,
		Code:     event.NoticeCodeSessionRecoveryForked,
		Text:     "local session maintenance",
	})
	forwarder.Emit(event.Event{
		Kind:  event.Notice,
		Level: event.LevelWarn,
		Text:  "please retry the user action",
	})

	if got := len(forwarder.queue); got != 1 {
		t.Fatalf("queued messages = %d, want only the actionable user warning", got)
	}
	if got := <-forwarder.queue; got != "⚠️ please retry the user action" {
		t.Fatalf("queued message = %q, want the ordinary user warning", got)
	}
}
