package usecase_test

// T219 — M6 invite integration (spec §9.16 IV-01～IV-13)

import (
	"context"
	"testing"

	"apps/backend/internal/module/member/domain"
	"apps/backend/internal/module/member/usecase"

	"github.com/stretchr/testify/require"
)

func TestIV_01_GetMyNetwork(t *testing.T) {
	store, svc := newM1Svc()
	m, _ := seedMember(t, store, svc, "iv01@test.local")
	net, err := svc.GetMyNetwork(context.Background(), m.UID)
	require.NoError(t, err)
	require.Equal(t, m.UID, net.Me.UID)
	require.NotEmpty(t, net.Me.InviteCode)
	require.Nil(t, net.Me.ParentUID)
	require.Empty(t, net.Downlines)
}

func TestIV_02_ApplyLegalCode(t *testing.T) {
	store, svc := newM1Svc()
	inv, _ := seedMember(t, store, svc, "iv02-inv@test.local")
	me, _ := seedMember(t, store, svc, "iv02-me@test.local")
	net, err := svc.ApplyInviteCode(context.Background(), me.UID, inv.InviteCode)
	require.NoError(t, err)
	require.NotNil(t, net.Me.ParentUID)
	require.Equal(t, inv.UID, *net.Me.ParentUID)
	require.NotNil(t, net.Upline)
	require.Equal(t, inv.UID, net.Upline.UID)
}

func TestIV_03_AlreadyBound(t *testing.T) {
	store, svc := newM1Svc()
	a, _ := seedMember(t, store, svc, "iv03a@test.local")
	b, _ := seedMember(t, store, svc, "iv03b@test.local")
	c, _ := seedMember(t, store, svc, "iv03c@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
	require.NoError(t, err)
	_, err = svc.ApplyInviteCode(context.Background(), b.UID, c.InviteCode)
	require.ErrorIs(t, err, domain.ErrInviteAlreadyBound)
}

func TestIV_04_ApplySelfCode(t *testing.T) {
	store, svc := newM1Svc()
	me, _ := seedMember(t, store, svc, "iv04@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), me.UID, me.InviteCode)
	require.ErrorIs(t, err, domain.ErrInviteCodeSelf)
}

func TestIV_05_ApplyUnknownCode(t *testing.T) {
	store, svc := newM1Svc()
	me, _ := seedMember(t, store, svc, "iv05@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), me.UID, "NO-SUCH-CODE")
	require.ErrorIs(t, err, domain.ErrInviteCodeNotFound)
}

func TestIV_06_ApplyEmptyCode(t *testing.T) {
	store, svc := newM1Svc()
	me, _ := seedMember(t, store, svc, "iv06@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), me.UID, "   ")
	require.ErrorIs(t, err, domain.ErrInviteCodeRequired)
}

func TestIV_07_DownlinesCount(t *testing.T) {
	store, svc := newM1Svc()
	inv, _ := seedMember(t, store, svc, "iv07-inv@test.local")
	d1, _ := seedMember(t, store, svc, "iv07-d1@test.local")
	d2, _ := seedMember(t, store, svc, "iv07-d2@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), d1.UID, inv.InviteCode)
	require.NoError(t, err)
	_, err = svc.ApplyInviteCode(context.Background(), d2.UID, inv.InviteCode)
	require.NoError(t, err)
	net, err := svc.GetMyNetwork(context.Background(), inv.UID)
	require.NoError(t, err)
	require.Len(t, net.Downlines, 2)
	require.Equal(t, 2, net.TotalDownlineCount)
	require.Equal(t, 2, net.Me.DownlineCount)
}

func TestIV_08_GetTree(t *testing.T) {
	store, svc := newM1Svc()
	root, _ := seedMember(t, store, svc, "iv08-root@test.local")
	child, _ := seedMember(t, store, svc, "iv08-c@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), child.UID, root.InviteCode)
	require.NoError(t, err)
	forest, err := svc.GetInviteTree(context.Background())
	require.NoError(t, err)
	require.NotEmpty(t, forest)
	// find root
	var found bool
	var walk func([]usecase.InviteTreeNode)
	walk = func(nodes []usecase.InviteTreeNode) {
		for _, n := range nodes {
			if n.UID == root.UID {
				found = true
				require.NotEmpty(t, n.Children)
			}
			walk(n.Children)
		}
	}
	walk(forest)
	require.True(t, found)
}

func TestIV_09_MoveLegal(t *testing.T) {
	store, svc := newM1Svc()
	admin, _ := seedAdmin(t, store, svc, "iv09-admin@test.local")
	a, _ := seedMember(t, store, svc, "iv09-a@test.local")
	b, _ := seedMember(t, store, svc, "iv09-b@test.local")
	// b under a
	_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
	require.NoError(t, err)
	// move b to root
	brief, err := svc.MoveInviteMember(context.Background(), admin.UID, b.UID, nil)
	require.NoError(t, err)
	require.Nil(t, brief.ParentUID)
}

func TestIV_10_NoCycle(t *testing.T) {
	store, svc := newM1Svc()
	admin, _ := seedAdmin(t, store, svc, "iv10-admin@test.local")
	a, _ := seedMember(t, store, svc, "iv10-a@test.local")
	b, _ := seedMember(t, store, svc, "iv10-b@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
	require.NoError(t, err)
	// move a under b → cycle
	_, err = svc.MoveInviteMember(context.Background(), admin.UID, a.UID, &b.UID)
	require.ErrorIs(t, err, domain.ErrInviteCycle)
}

func TestIV_11_MoveToSelf(t *testing.T) {
	store, svc := newM1Svc()
	admin, _ := seedAdmin(t, store, svc, "iv11-admin@test.local")
	a, _ := seedMember(t, store, svc, "iv11-a@test.local")
	_, err := svc.MoveInviteMember(context.Background(), admin.UID, a.UID, &a.UID)
	require.ErrorIs(t, err, domain.ErrInviteSelfParent)
}

func TestIV_12_MemberCannotMove(t *testing.T) {
	store, svc := newM1Svc()
	mem, _ := seedMember(t, store, svc, "iv12-mem@test.local")
	a, _ := seedMember(t, store, svc, "iv12-a@test.local")
	b, _ := seedMember(t, store, svc, "iv12-b@test.local")
	_, err := svc.MoveInviteMember(context.Background(), mem.UID, a.UID, &b.UID)
	require.ErrorIs(t, err, domain.ErrInviteNeedAdmin)
}

func TestIV_13_ListParentCandidatesExcludeSubtree(t *testing.T) {
	store, svc := newM1Svc()
	a, _ := seedMember(t, store, svc, "iv13-a@test.local")
	b, _ := seedMember(t, store, svc, "iv13-b@test.local")
	c, _ := seedMember(t, store, svc, "iv13-c@test.local")
	_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
	require.NoError(t, err)
	_, err = svc.ApplyInviteCode(context.Background(), c.UID, b.InviteCode)
	require.NoError(t, err)
	// exclude subtree of a → no a,b,c
	cands, err := svc.ListParentCandidates(context.Background(), a.UID)
	require.NoError(t, err)
	for _, x := range cands {
		require.NotEqual(t, a.UID, x.UID)
		require.NotEqual(t, b.UID, x.UID)
		require.NotEqual(t, c.UID, x.UID)
	}
}
