package network_test

import (
	"context"
	"os"
	"testing"

	pbaero "github.com/flowy-live/llink/genproto/aero"
	mock_billing "github.com/flowy-live/llink/internal/billing/mocks"
	"github.com/flowy-live/llink/internal/human"
	mock_human "github.com/flowy-live/llink/internal/human/mocks"
	mock_livestore "github.com/flowy-live/llink/internal/livestore/mocks"
	"github.com/flowy-live/llink/internal/network"
	"github.com/flowy-live/llink/internal/testhelper"
	"github.com/flowy-live/llink/internal/testhelper/mocks/aero"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"
)

var dbPool *pgxpool.Pool

func TestMain(m *testing.M) {
	dbPool = testhelper.SetupTestDB()
	defer testhelper.TeardownTestDB()

	ret := m.Run()
	os.Exit(ret)
}

func newTestService(t *testing.T) network.Service {
	ctrl := gomock.NewController(t)
	mockAero := aero.NewMockPrimaryClient(ctrl)
	mockAero.EXPECT().
		ShootEmail(gomock.Any(), gomock.Any()).
		Return(&pbaero.ShootEmailResponse{}, nil).
		AnyTimes()

	mockBilling := mock_billing.NewMockService(ctrl)
	mockBilling.EXPECT().SyncSeats(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()

	mockPub := mock_livestore.NewMockMembershipPublisher(ctrl)
	mockPub.EXPECT().Add(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
	mockPub.EXPECT().Remove(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()

	mockHuman := mock_human.NewMockService(ctrl)
	mockHuman.EXPECT().GetByID(gomock.Any(), gomock.Any()).Return(&human.Human{
		ID:                        "test_human",
		Email:                     "test@gmail.com",
		EmailPrefix:               "test",
		EmailNotificationsEnabled: false,
	}, nil).AnyTimes()

	return network.NewService(dbPool, mockAero, mockBilling, mockPub, mockHuman)
}

func TestNetworkService(t *testing.T) {
	ctx := context.Background()
	svc := newTestService(t)

	adminHumanId := "human_admin123"
	member1HumanId := "human_member1abc"
	member2HumanId := "human_member2def"
	strangerHumanId := "human_stranger789"

	// Test Create
	createdNetwork, err := svc.Create(ctx, "Test Network", adminHumanId)
	assert.NoError(t, err)
	assert.NotEmpty(t, createdNetwork.ID)
	assert.Equal(t, "Test Network", createdNetwork.Name)
	assert.Equal(t, adminHumanId, createdNetwork.AdminHumanId)
	assert.NotZero(t, createdNetwork.CreatedAt)

	// Test GetByID
	foundNetwork, err := svc.GetByID(ctx, createdNetwork.ID)
	assert.NoError(t, err)
	assert.Equal(t, createdNetwork.ID, foundNetwork.ID)
	assert.Equal(t, createdNetwork.Name, foundNetwork.Name)
	assert.Equal(t, createdNetwork.AdminHumanId, foundNetwork.AdminHumanId)

	// Test GetByID with non-existent id
	_, err = svc.GetByID(ctx, "network_nonexistent")
	assert.Error(t, err)
	assert.ErrorIs(t, err, network.ErrNotFound)

	// Test SetName
	err = svc.SetName(ctx, createdNetwork.ID, "Updated Network Name")
	assert.NoError(t, err)

	// Verify name was updated
	updatedNetwork, err := svc.GetByID(ctx, createdNetwork.ID)
	assert.NoError(t, err)
	assert.Equal(t, "Updated Network Name", updatedNetwork.Name)

	// Test SetName with non-existent id
	err = svc.SetName(ctx, "network_nonexistent", "New Name")
	assert.Error(t, err)
	assert.ErrorIs(t, err, network.ErrNotFound)

	// Test AddMembers
	err = svc.AddMembers(ctx, createdNetwork.ID, []string{member1HumanId, member2HumanId})
	assert.NoError(t, err)

	// Test ListForHuman - should find network for admin
	networks, err := svc.ListForHuman(ctx, adminHumanId)
	assert.NoError(t, err)
	assert.Len(t, networks, 1)
	assert.Equal(t, createdNetwork.ID, networks[0].ID)

	// Test ListForHuman - should find network for member
	networks, err = svc.ListForHuman(ctx, member1HumanId)
	assert.NoError(t, err)
	assert.Len(t, networks, 1)
	assert.Equal(t, createdNetwork.ID, networks[0].ID)

	// Test ListForHuman - should return empty for non-member
	networks, err = svc.ListForHuman(ctx, strangerHumanId)
	assert.NoError(t, err)
	assert.Len(t, networks, 0)

	// Test RemoveMember
	err = svc.RemoveMember(ctx, createdNetwork.ID, member1HumanId)
	assert.NoError(t, err)

	// Verify member was removed
	networks, err = svc.ListForHuman(ctx, member1HumanId)
	assert.NoError(t, err)
	assert.Len(t, networks, 0)

	// member2 should still have access
	networks, err = svc.ListForHuman(ctx, member2HumanId)
	assert.NoError(t, err)
	assert.Len(t, networks, 1)

	// Create another network and verify ListForHuman returns multiple
	network2, err := svc.Create(ctx, "Second Network", member2HumanId)
	assert.NoError(t, err)

	networks, err = svc.ListForHuman(ctx, member2HumanId)
	assert.NoError(t, err)
	assert.Len(t, networks, 2)

	// Verify both networks are returned
	networkIDs := []string{networks[0].ID, networks[1].ID}
	assert.Contains(t, networkIDs, createdNetwork.ID)
	assert.Contains(t, networkIDs, network2.ID)
}

func TestNetworkInvitations(t *testing.T) {
	ctx := context.Background()
	svc := newTestService(t)

	adminHumanId := "human_invtest_admin"
	inviteeEmail := "invitee@example.com"
	inviteeHumanId := "human_invitee123"

	// Create a network
	net, err := svc.Create(ctx, "Invitation Test Network", adminHumanId)
	assert.NoError(t, err)

	// Invite by email
	err = svc.InviteByEmail(ctx, net.ID, []string{inviteeEmail})
	assert.NoError(t, err)

	// List invitations for email
	invitations, err := svc.ListInvitationsForEmail(ctx, inviteeEmail)
	assert.NoError(t, err)
	assert.Len(t, invitations, 1)
	assert.Equal(t, net.ID, invitations[0].NetworkID)
	assert.Equal(t, inviteeEmail, invitations[0].Email)

	// List invitations for network
	invitations, err = svc.ListInvitationsForNetwork(ctx, net.ID)
	assert.NoError(t, err)
	assert.Len(t, invitations, 1)

	// Duplicate invite is idempotent
	err = svc.InviteByEmail(ctx, net.ID, []string{inviteeEmail})
	assert.NoError(t, err)
	invitations, err = svc.ListInvitationsForNetwork(ctx, net.ID)
	assert.NoError(t, err)
	assert.Len(t, invitations, 1)

	// Accept invitation
	err = svc.AcceptInvitation(ctx, net.ID, inviteeEmail, inviteeHumanId)
	assert.NoError(t, err)

	// Invitation should be removed
	invitations, err = svc.ListInvitationsForEmail(ctx, inviteeEmail)
	assert.NoError(t, err)
	assert.Len(t, invitations, 0)

	// Human should now be a member
	isMember, err := svc.IsMember(ctx, net.ID, inviteeHumanId)
	assert.NoError(t, err)
	assert.True(t, isMember)

	// Test revoke invitation
	revokeEmail := "revokee@example.com"
	err = svc.InviteByEmail(ctx, net.ID, []string{revokeEmail})
	assert.NoError(t, err)

	err = svc.RevokeInvitation(ctx, net.ID, revokeEmail)
	assert.NoError(t, err)

	invitations, err = svc.ListInvitationsForEmail(ctx, revokeEmail)
	assert.NoError(t, err)
	assert.Len(t, invitations, 0)
}
