package usecase

import (
	"context"
	"sort"
	"strings"

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

// InviteMemberBrief — 邀請網絡節點摘要（API 層再轉 types）
type InviteMemberBrief struct {
	UID                int64
	Email              string
	DisplayName        string
	InviteCode         string
	ParentUID          *int64
	Roles              []string
	Status             string
	DownlineCount      int
	TotalDownlineCount int
	Depth              int
	JoinedAt           int64
	AvatarURL          string
}

// InviteTreeNode — 森林節點
type InviteTreeNode struct {
	InviteMemberBrief
	Children []InviteTreeNode
}

// MyInviteNetwork — 會員自己的碼 + 上下線
type MyInviteNetwork struct {
	Me                 InviteMemberBrief
	Upline             *InviteMemberBrief
	Downlines          []InviteMemberBrief
	TotalDownlineCount int
}

// GetMyNetwork — IV-01 / IV-07
func (s *AccountService) GetMyNetwork(ctx context.Context, uid int64) (*MyInviteNetwork, error) {
	all, err := s.Repo.ListAllMembers(ctx)
	if err != nil {
		return nil, err
	}
	briefs := buildInviteBriefs(all)
	me, ok := briefs[uid]
	if !ok {
		return nil, domain.ErrNotFound
	}
	var upline *InviteMemberBrief
	if me.ParentUID != nil {
		if u, ok := briefs[*me.ParentUID]; ok {
			cp := u
			upline = &cp
		}
	}
	var down []InviteMemberBrief
	for _, m := range all {
		if m.ParentUID != nil && *m.ParentUID == uid {
			if b, ok := briefs[m.UID]; ok {
				down = append(down, b)
			}
		}
	}
	sort.Slice(down, func(i, j int) bool { return down[i].JoinedAt > down[j].JoinedAt })
	return &MyInviteNetwork{
		Me:                 me,
		Upline:             upline,
		Downlines:          down,
		TotalDownlineCount: me.TotalDownlineCount,
	}, nil
}

// ApplyInviteCode — IV-02～06
func (s *AccountService) ApplyInviteCode(ctx context.Context, uid int64, rawCode string) (*MyInviteNetwork, error) {
	code := strings.ToUpper(strings.TrimSpace(rawCode))
	if code == "" {
		return nil, domain.ErrInviteCodeRequired
	}
	me, err := s.Repo.FindByUID(ctx, uid)
	if err != nil {
		return nil, err
	}
	if me.ParentUID != nil && *me.ParentUID > 0 {
		return nil, domain.ErrInviteAlreadyBound
	}
	inviter, err := s.Repo.FindByInviteCode(ctx, code)
	if err != nil {
		if err == domain.ErrNotFound {
			return nil, domain.ErrInviteCodeNotFound
		}
		return nil, err
	}
	if inviter.UID == uid {
		return nil, domain.ErrInviteCodeSelf
	}
	// 防環：邀請人不可為自己的子孫
	all, err := s.Repo.ListAllMembers(ctx)
	if err != nil {
		return nil, err
	}
	if isDescendantOf(uid, inviter.UID, all) {
		return nil, domain.ErrInviteCycle
	}
	p := inviter.UID
	me.ParentUID = &p
	me.UpdatedAt = domain.NowNano()
	if err := s.Repo.UpdateMember(ctx, me); err != nil {
		return nil, err
	}
	return s.GetMyNetwork(ctx, uid)
}

// GetInviteTree — IV-08 admin
func (s *AccountService) GetInviteTree(ctx context.Context) ([]InviteTreeNode, error) {
	all, err := s.Repo.ListAllMembers(ctx)
	if err != nil {
		return nil, err
	}
	return buildInviteForest(all), nil
}

// MoveInviteMember — IV-09～12
// newParentUID nil = 獨立根
func (s *AccountService) MoveInviteMember(ctx context.Context, actorUID, targetUID int64, newParentUID *int64) (*InviteMemberBrief, error) {
	actor, err := s.Repo.FindByUID(ctx, actorUID)
	if err != nil {
		return nil, err
	}
	if !actor.IsAdmin() {
		return nil, domain.ErrInviteNeedAdmin
	}
	target, err := s.Repo.FindByUID(ctx, targetUID)
	if err != nil {
		return nil, domain.ErrNotFound
	}
	if newParentUID != nil && *newParentUID == targetUID {
		return nil, domain.ErrInviteSelfParent
	}
	if newParentUID != nil && *newParentUID > 0 {
		parent, err := s.Repo.FindByUID(ctx, *newParentUID)
		if err != nil {
			return nil, domain.ErrInviteParentMiss
		}
		_ = parent
		all, err := s.Repo.ListAllMembers(ctx)
		if err != nil {
			return nil, err
		}
		// 新父不可是 target 的子孫（含自己已檢查）
		if isDescendantOf(targetUID, *newParentUID, all) {
			return nil, domain.ErrInviteCycle
		}
		target.ParentUID = newParentUID
	} else {
		target.ParentUID = nil
	}
	target.UpdatedAt = domain.NowNano()
	if err := s.Repo.UpdateMember(ctx, target); err != nil {
		return nil, err
	}
	all, err := s.Repo.ListAllMembers(ctx)
	if err != nil {
		return nil, err
	}
	briefs := buildInviteBriefs(all)
	b := briefs[targetUID]
	return &b, nil
}

// ListParentCandidates — IV-13 排除子樹
func (s *AccountService) ListParentCandidates(ctx context.Context, excludeSubtreeRootUID int64) ([]InviteMemberBrief, error) {
	all, err := s.Repo.ListAllMembers(ctx)
	if err != nil {
		return nil, err
	}
	exclude := map[int64]struct{}{}
	if excludeSubtreeRootUID > 0 {
		exclude[excludeSubtreeRootUID] = struct{}{}
		for _, m := range all {
			if isDescendantOf(excludeSubtreeRootUID, m.UID, all) {
				exclude[m.UID] = struct{}{}
			}
		}
	}
	briefs := buildInviteBriefs(all)
	var out []InviteMemberBrief
	for _, m := range all {
		if _, skip := exclude[m.UID]; skip {
			continue
		}
		if b, ok := briefs[m.UID]; ok {
			out = append(out, b)
		}
	}
	sort.Slice(out, func(i, j int) bool {
		return strings.ToLower(out[i].DisplayName) < strings.ToLower(out[j].DisplayName)
	})
	return out, nil
}

// --- helpers ---

func buildInviteBriefs(members []*domain.Member) map[int64]InviteMemberBrief {
	children := map[int64][]int64{}
	byUID := map[int64]*domain.Member{}
	for _, m := range members {
		byUID[m.UID] = m
		if m.ParentUID != nil && *m.ParentUID > 0 {
			p := *m.ParentUID
			children[p] = append(children[p], m.UID)
		}
	}
	totalMemo := map[int64]int{}
	var totalOf func(uid int64, stack map[int64]struct{}) int
	totalOf = func(uid int64, stack map[int64]struct{}) int {
		if v, ok := totalMemo[uid]; ok {
			return v
		}
		if _, loop := stack[uid]; loop {
			return 0
		}
		stack[uid] = struct{}{}
		kids := children[uid]
		n := len(kids)
		for _, k := range kids {
			n += totalOf(k, stack)
		}
		delete(stack, uid)
		totalMemo[uid] = n
		return n
	}
	depthMemo := map[int64]int{}
	var depthOf func(uid int64, stack map[int64]struct{}) int
	depthOf = func(uid int64, stack map[int64]struct{}) int {
		if v, ok := depthMemo[uid]; ok {
			return v
		}
		if _, loop := stack[uid]; loop {
			return 0
		}
		m := byUID[uid]
		if m == nil || m.ParentUID == nil || *m.ParentUID <= 0 || byUID[*m.ParentUID] == nil {
			depthMemo[uid] = 0
			return 0
		}
		stack[uid] = struct{}{}
		d := 1 + depthOf(*m.ParentUID, stack)
		delete(stack, uid)
		depthMemo[uid] = d
		return d
	}

	out := make(map[int64]InviteMemberBrief, len(members))
	for _, m := range members {
		b := InviteMemberBrief{
			UID: m.UID, Email: m.Email, DisplayName: m.DisplayName,
			InviteCode: strings.TrimSpace(m.InviteCode), Roles: append([]string{}, m.Roles...),
			Status: m.Status, JoinedAt: m.CreatedAt, AvatarURL: m.AvatarURL,
			DownlineCount: len(children[m.UID]), TotalDownlineCount: totalOf(m.UID, map[int64]struct{}{}),
			Depth: depthOf(m.UID, map[int64]struct{}{}),
		}
		if m.ParentUID != nil && *m.ParentUID > 0 && byUID[*m.ParentUID] != nil {
			p := *m.ParentUID
			b.ParentUID = &p
		}
		if b.InviteCode == "" {
			b.InviteCode = "—"
		}
		out[m.UID] = b
	}
	return out
}

func buildInviteForest(members []*domain.Member) []InviteTreeNode {
	briefs := buildInviteBriefs(members)
	byParent := map[int64][]InviteMemberBrief{} // key 0 = roots
	for _, b := range briefs {
		var pk int64
		if b.ParentUID != nil {
			pk = *b.ParentUID
			if _, ok := briefs[pk]; !ok {
				pk = 0 // 孤兒當根
			}
		}
		byParent[pk] = append(byParent[pk], b)
	}
	var build func(uid int64) InviteTreeNode
	build = func(uid int64) InviteTreeNode {
		b := briefs[uid]
		n := InviteTreeNode{InviteMemberBrief: b, Children: nil}
		kids := byParent[uid]
		sort.Slice(kids, func(i, j int) bool {
			return strings.ToLower(kids[i].DisplayName) < strings.ToLower(kids[j].DisplayName)
		})
		for _, k := range kids {
			n.Children = append(n.Children, build(k.UID))
		}
		return n
	}
	roots := byParent[0]
	sort.Slice(roots, func(i, j int) bool {
		return strings.ToLower(roots[i].DisplayName) < strings.ToLower(roots[j].DisplayName)
	})
	var forest []InviteTreeNode
	for _, r := range roots {
		forest = append(forest, build(r.UID))
	}
	return forest
}

// isDescendantOf — whether candidate is under rootUID's subtree (not root itself)
func isDescendantOf(rootUID, candidateUID int64, members []*domain.Member) bool {
	if rootUID == candidateUID {
		return false
	}
	parentOf := map[int64]int64{}
	for _, m := range members {
		if m.ParentUID != nil && *m.ParentUID > 0 {
			parentOf[m.UID] = *m.ParentUID
		}
	}
	seen := map[int64]struct{}{}
	cur := candidateUID
	for cur != 0 {
		if cur == rootUID {
			return true
		}
		if _, loop := seen[cur]; loop {
			return false
		}
		seen[cur] = struct{}{}
		p, ok := parentOf[cur]
		if !ok {
			return false
		}
		cur = p
	}
	return false
}
