//go:build e2e

package channel

import (
	"context"
	"testing"
	"time"

	"github.com/tidbcloud/AgentTeam/e2e/assertions"
	"github.com/tidbcloud/AgentTeam/e2e/client"
	"github.com/tidbcloud/AgentTeam/e2e/testkit"
)

func TestCreatePrivateChannel(t *testing.T) {
	sc := testkit.NewSingleUserScenario(t)

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	channelName := testkit.UniqueChannelName("ops")
	created, status, raw, err := sc.API.CreateChannel(ctx, channelName, "e2e private ops channel")
	if err != nil {
		t.Fatalf("create channel: %v", err)
	}
	assertions.RequireStatus(t, status, 200, raw)
	if created.ID == "" || created.Name != channelName {
		t.Fatalf("unexpected create response: id=%s name=%s", created.ID, created.Name)
	}

	found := testkit.RequireChannelByName(t, sc.API, channelName)
	if found.ID != created.ID {
		t.Fatalf("channel list mismatch: got %s want %s", found.ID, created.ID)
	}
	t.Logf("private channel created: id=%s name=%s", created.ID, created.Name)
}

func TestAllChannelBroadcast(t *testing.T) {
	sc := testkit.NewHostPeerScenario(t)
	testkit.LogWorkspaceSession(t, "all-channel broadcast", sc.Host.UserID, sc.Workspace)

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	allChannel := testkit.RequireAllChannel(t, sc.HostAPI)
	content := testkit.UniqueMessageText("all-broadcast")
	sent, status, raw, err := sc.HostAPI.CreateMessage(ctx, allChannel.ID, content)
	if err != nil {
		t.Fatalf("broadcast to #all: %v", err)
	}
	assertions.RequireStatus(t, status, 200, raw)
	t.Logf("host broadcast messageId=%s", sent.ID)

	testkit.AssertMessageInChannel(t, sc.Peer.API, allChannel.ID, content)
	t.Logf("peer received #all broadcast")
}

func TestPrivateChannelMessageRoundTrip(t *testing.T) {
	sc := testkit.NewSingleUserScenario(t)

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	channelName := testkit.UniqueChannelName("topic")
	createdChannel, status, raw, err := sc.API.CreateChannel(ctx, channelName, "e2e private topic channel")
	if err != nil {
		t.Fatalf("create private channel: %v", err)
	}
	assertions.RequireStatus(t, status, 200, raw)

	content := testkit.UniqueMessageText("private-channel-msg")
	sent, status, raw, err := sc.API.CreateMessage(ctx, createdChannel.ID, content)
	if err != nil {
		t.Fatalf("send private channel message: %v", err)
	}
	assertions.RequireStatus(t, status, 200, raw)
	if sent.ChannelID != createdChannel.ID {
		t.Fatalf("unexpected channel id: got %s want %s", sent.ChannelID, createdChannel.ID)
	}

	testkit.AssertMessageInChannel(t, sc.API, createdChannel.ID, content)
	t.Logf("private channel message round-trip passed: channel=%s message=%s", createdChannel.ID, sent.ID)
}

func TestChannelMembershipRoutingMatrix(t *testing.T) {
	sc := testkit.NewHostPeerScenario(t)
	ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
	defer cancel()

	peerB := testkit.RequirePeerInWorkspace(t, sc.Env, sc.Host, sc.Workspace, "channel-matrix-peer-b")
	privateChannel, status, raw, err := sc.HostAPI.CreateChannel(ctx, testkit.UniqueChannelName("ch-matrix"), "channel membership routing matrix")
	if err != nil {
		t.Fatalf("create private channel: %v", err)
	}
	assertions.RequireStatus(t, status, 200, raw)

	t.Run("01-host-visible-peer-hidden", func(t *testing.T) {
		hostChannels, s, r, e := sc.HostAPI.ListChannels(ctx)
		if e != nil {
			t.Fatalf("host list channels: %v", e)
		}
		assertions.RequireStatus(t, s, 200, r)
		if !channelListContains(hostChannels, privateChannel.ID) {
			t.Fatalf("host should see private channel %s", privateChannel.ID)
		}

		peerChannels, s, r, e := sc.Peer.API.ListChannels(ctx)
		if e != nil {
			t.Fatalf("peerA list channels: %v", e)
		}
		assertions.RequireStatus(t, s, 200, r)
		if channelListContains(peerChannels, privateChannel.ID) {
			t.Fatalf("peerA should not see private channel before add")
		}
	})

	var seed string
	t.Run("02-host-send-seed-before-add", func(t *testing.T) {
		seed = testkit.UniqueMessageText("ch-matrix-seed")
		_, s, r, e := sc.HostAPI.CreateMessage(ctx, privateChannel.ID, seed)
		if e != nil {
			t.Fatalf("host send seed message: %v", e)
		}
		assertions.RequireStatus(t, s, 200, r)
	})

	t.Run("03-peerA-cannot-read-or-send-before-add", func(t *testing.T) {
		assertChannelForbiddenRead(t, ctx, sc.Peer.API, privateChannel.ID, "peerA-before-add-read")
		assertChannelForbiddenSend(t, ctx, sc.Peer.API, privateChannel.ID, "peerA-before-add-send")
	})

	t.Run("04-add-peerA-can-read-history-and-reply", func(t *testing.T) {
		s, r, e := sc.HostAPI.AddChannelUserMember(ctx, privateChannel.ID, sc.Peer.UserID)
		if e != nil {
			t.Fatalf("add peerA to channel: %v", e)
		}
		assertions.RequireStatus(t, s, 200, r)
		testkit.AssertMessageInChannel(t, sc.Peer.API, privateChannel.ID, seed)

		peerReply := testkit.UniqueMessageText("ch-matrix-peerA-reply")
		_, s, r, e = sc.Peer.API.CreateMessage(ctx, privateChannel.ID, peerReply)
		if e != nil {
			t.Fatalf("peerA send reply: %v", e)
		}
		assertions.RequireStatus(t, s, 200, r)
		testkit.AssertMessageInChannel(t, sc.HostAPI, privateChannel.ID, peerReply)
	})

	t.Run("05-peerB-still-isolated", func(t *testing.T) {
		assertChannelForbiddenRead(t, ctx, peerB.API, privateChannel.ID, "peerB-before-add-read")
		assertChannelForbiddenSend(t, ctx, peerB.API, privateChannel.ID, "peerB-before-add-send")
	})

	t.Run("06-add-peerB-three-way-visible", func(t *testing.T) {
		s, r, e := sc.HostAPI.AddChannelUserMember(ctx, privateChannel.ID, peerB.UserID)
		if e != nil {
			t.Fatalf("add peerB to channel: %v", e)
		}
		assertions.RequireStatus(t, s, 200, r)

		peerBText := testkit.UniqueMessageText("ch-matrix-peerB-msg")
		_, s, r, e = peerB.API.CreateMessage(ctx, privateChannel.ID, peerBText)
		if e != nil {
			t.Fatalf("peerB send message: %v", e)
		}
		assertions.RequireStatus(t, s, 200, r)
		testkit.AssertMessageInChannel(t, sc.Peer.API, privateChannel.ID, peerBText)
		testkit.AssertMessageInChannel(t, sc.HostAPI, privateChannel.ID, peerBText)
	})
}

func assertChannelForbiddenRead(t *testing.T, ctx context.Context, api *client.Client, channelID, label string) {
	t.Helper()
	_, status, raw, err := api.ListChannelMessages(ctx, channelID, testkit.MessageHistoryLimit())
	if err != nil {
		t.Fatalf("read forbidden channel (%s): %v", label, err)
	}
	if status == 200 {
		t.Fatalf("expected non-200 for forbidden read (%s), raw=%s", label, string(raw))
	}
}

func assertChannelForbiddenSend(t *testing.T, ctx context.Context, api *client.Client, channelID, label string) {
	t.Helper()
	_, status, raw, err := api.CreateMessage(ctx, channelID, testkit.UniqueMessageText(label))
	if err != nil {
		t.Fatalf("send forbidden channel (%s): %v", label, err)
	}
	if status == 200 {
		t.Fatalf("expected non-200 for forbidden send (%s), raw=%s", label, string(raw))
	}
}

func channelListContains(channels []client.Channel, channelID string) bool {
	for _, channel := range channels {
		if channel.ID == channelID {
			return true
		}
	}
	return false
}
