package channel

import (
	"context"
	"fmt"
	"log/slog"
	"testing"
)

// mockAdapter is used for inbound handleInbound tests.
type mockAdapter struct {
	sentMessages []OutboundMessage
	streamEvents []StreamEvent
}

func (m *mockAdapter) Type() ChannelType { return ChannelType("test") }
func (m *mockAdapter) Descriptor() Descriptor {
	return Descriptor{
		Type:        ChannelType("test"),
		DisplayName: "Test",
		Capabilities: ChannelCapabilities{
			Text:      true,
			Reply:     true,
			Streaming: true,
		},
	}
}
func (m *mockAdapter) Send(ctx context.Context, cfg ChannelConfig, msg OutboundMessage) error {
	m.sentMessages = append(m.sentMessages, msg)
	return nil
}

func (m *mockAdapter) OpenStream(ctx context.Context, cfg ChannelConfig, target string, opts StreamOptions) (OutboundStream, error) {
	return &mockAdapterStream{adapter: m}, nil
}

type mockAdapterStream struct {
	adapter *mockAdapter
}

func (s *mockAdapterStream) Push(ctx context.Context, event StreamEvent) error {
	if s == nil || s.adapter == nil {
		return nil
	}
	s.adapter.streamEvents = append(s.adapter.streamEvents, event)
	if event.Type == StreamEventFinal && event.Final != nil && !event.Final.Message.IsEmpty() {
		s.adapter.sentMessages = append(s.adapter.sentMessages, OutboundMessage{
			Target:  "stream-target",
			Message: event.Final.Message,
		})
	}
	return nil
}

func (s *mockAdapterStream) Close(ctx context.Context) error {
	return nil
}

type fakeInboundProcessor struct {
	resp   *OutboundMessage
	err    error
	gotCfg ChannelConfig
	gotMsg InboundMessage
}

func (f *fakeInboundProcessor) HandleInbound(ctx context.Context, cfg ChannelConfig, msg InboundMessage, sender StreamReplySender) error {
	f.gotCfg = cfg
	f.gotMsg = msg
	if f.err != nil {
		return f.err
	}
	if f.resp == nil {
		return nil
	}
	if sender == nil {
		return fmt.Errorf("sender missing")
	}
	return sender.Send(ctx, *f.resp)
}

type fakeInboundStreamProcessor struct{}

func (f *fakeInboundStreamProcessor) HandleInbound(ctx context.Context, cfg ChannelConfig, msg InboundMessage, sender StreamReplySender) error {
	stream, err := sender.OpenStream(ctx, "stream-target", StreamOptions{})
	if err != nil {
		return err
	}
	if err := stream.Push(ctx, StreamEvent{
		Type:  StreamEventDelta,
		Delta: "partial",
	}); err != nil {
		return err
	}
	if err := stream.Push(ctx, StreamEvent{
		Type: StreamEventFinal,
		Final: &StreamFinalizePayload{
			Message: Message{Text: "stream-final"},
		},
	}); err != nil {
		return err
	}
	return stream.Close(ctx)
}

func TestManager_handleInbound(t *testing.T) {
	logger := slog.Default()

	t.Run("with_reply_sends_successfully", func(t *testing.T) {
		processor := &fakeInboundProcessor{
			resp: &OutboundMessage{
				Target: "target-id",
				Message: Message{
					Text: "AI reply content",
				},
			},
		}

		reg := NewRegistry()
		m := NewManager(logger, reg, &fakeConfigStore{}, processor)
		adapter := &mockAdapter{}
		m.RegisterAdapter(adapter)

		cfg := ChannelConfig{ID: "bot-1", BotID: "bot-1", ChannelType: ChannelType("test")}
		msg := InboundMessage{
			Channel:     ChannelType("test"),
			Message:     Message{Text: "hello"},
			ReplyTarget: "target-id",
			Conversation: Conversation{
				ID:   "chat-1",
				Type: "p2p",
			},
		}

		err := m.handleInbound(context.Background(), cfg, msg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if len(adapter.sentMessages) != 1 {
			t.Fatalf("expected 1 reply sent, got %d", len(adapter.sentMessages))
		}
		if adapter.sentMessages[0].Message.PlainText() != "AI reply content" {
			t.Errorf("reply content mismatch: %s", adapter.sentMessages[0].Message.PlainText())
		}
		if adapter.sentMessages[0].Target != "target-id" {
			t.Errorf("reply target mismatch: %s", adapter.sentMessages[0].Target)
		}
	})

	t.Run("no_reply_does_not_send", func(t *testing.T) {
		processor := &fakeInboundProcessor{resp: nil}
		reg := NewRegistry()
		m := NewManager(logger, reg, &fakeConfigStore{}, processor)
		adapter := &mockAdapter{}
		m.RegisterAdapter(adapter)

		cfg := ChannelConfig{ID: "bot-1", BotID: "bot-1", ChannelType: ChannelType("test")}
		msg := InboundMessage{
			Channel:     ChannelType("test"),
			Message:     Message{Text: "hello"},
			ReplyTarget: "target-id",
		}

		err := m.handleInbound(context.Background(), cfg, msg)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if len(adapter.sentMessages) != 0 {
			t.Errorf("expected no reply sent, got %+v", adapter.sentMessages)
		}
	})

	t.Run("handler_error_returns_error", func(t *testing.T) {
		processor := &fakeInboundProcessor{err: context.Canceled}
		reg := NewRegistry()
		m := NewManager(logger, reg, &fakeConfigStore{}, processor)
		cfg := ChannelConfig{ID: "bot-1"}
		msg := InboundMessage{Message: Message{Text: "  "}} // whitespace-only message

		err := m.handleInbound(context.Background(), cfg, msg)
		if err == nil {
			t.Errorf("expected handler to return error")
		}
	})

	t.Run("stream sender forwards events", func(t *testing.T) {
		processor := &fakeInboundStreamProcessor{}
		reg := NewRegistry()
		m := NewManager(logger, reg, &fakeConfigStore{}, processor)
		adapter := &mockAdapter{}
		m.RegisterAdapter(adapter)

		cfg := ChannelConfig{ID: "bot-1", BotID: "bot-1", ChannelType: ChannelType("test")}
		msg := InboundMessage{
			Channel:     ChannelType("test"),
			Message:     Message{Text: "hello"},
			ReplyTarget: "stream-target",
			Conversation: Conversation{
				ID:   "chat-1",
				Type: "p2p",
			},
		}
		if err := m.handleInbound(context.Background(), cfg, msg); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(adapter.streamEvents) < 2 {
			t.Fatalf("expected at least two stream events, got %d", len(adapter.streamEvents))
		}
		if len(adapter.sentMessages) == 0 {
			t.Fatal("expected stream final message to be published")
		}
		if adapter.sentMessages[len(adapter.sentMessages)-1].Message.PlainText() != "stream-final" {
			t.Fatalf("unexpected stream final message: %s", adapter.sentMessages[len(adapter.sentMessages)-1].Message.PlainText())
		}
	})
}
