package blockbuilder

import (
	"fmt"
	"strings"
	"testing"

	"github.com/samber/lo"
	"github.com/stretchr/testify/assert"

	"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
	"github.com/anyproto/anytype-heart/util/pbtypes"
)

// BuildAST builds tree structure from flat list of blocks
func BuildAST(raw []*model.Block) *Block {
	blocks := lo.SliceToMap(raw, func(b *model.Block) (string, *Block) {
		return b.Id, &Block{block: b}
	})

	isChildOf := map[string]string{}
	for _, b := range raw {
		children := make([]*Block, 0, len(b.ChildrenIds))
		for _, id := range b.ChildrenIds {
			isChildOf[id] = b.Id

			if v, ok := blocks[id]; ok {
				children = append(children, v)
			}
		}

		blocks[b.Id].children = children
	}

	return findRootBlock(blocks, isChildOf)
}

func findRootBlock(blocks map[string]*Block, isChildOf map[string]string) *Block {
	for id, b := range blocks {
		// Root block has no parent
		if _, ok := isChildOf[id]; !ok {
			return b
		}
	}
	return nil
}

func dropBlockIDs(b *Block) {
	b.block.Id = ""
	for i := range b.block.ChildrenIds {
		b.block.ChildrenIds[i] = ""
	}

	if b.block.Restrictions == nil {
		b.block.Restrictions = &model.BlockRestrictions{}
	}

	for _, c := range b.children {
		dropBlockIDs(c)
	}
}

func isMarkContainsLink(m *model.BlockContentTextMark) bool {
	return m.Type == model.BlockContentTextMark_Mention ||
		m.Type == model.BlockContentTextMark_Object
}

func remapLinksInDataview(b *model.BlockContentDataview, newIDtoOldID map[string]string) {
	b.TargetObjectId = newIDtoOldID[b.TargetObjectId]

	for _, view := range b.Views {
		for _, filter := range view.Filters {
			newID := filter.Value.GetStringValue()
			if newID != "" {
				oldID := newIDtoOldID[newID]
				if oldID != "" {
					filter.Value = pbtypes.String(oldID)
				}
			}
		}
	}
}

func remapLinks(root *Block, idsMap map[string]string) {
	if b := root.block.GetLink(); b != nil {
		b.TargetBlockId = idsMap[b.TargetBlockId]
	}
	if b := root.block.GetText(); b != nil {
		for _, m := range b.Marks.Marks {
			if isMarkContainsLink(m) {
				m.Param = idsMap[m.Param]
			}
		}
	}
	if b := root.block.GetBookmark(); b != nil {
		if b.Type == model.LinkPreview_Page {
			b.TargetObjectId = idsMap[b.TargetObjectId]
		}
	}
	if b := root.block.GetDataview(); b != nil {
		remapLinksInDataview(b, idsMap)
	}

	for _, c := range root.children {
		remapLinks(c, idsMap)
	}
}

func AssertTreesEqual(t *testing.T, want, got []*model.Block) bool {
	wantTree := BuildAST(want)
	gotTree := BuildAST(got)

	dropBlockIDs(wantTree)
	dropBlockIDs(gotTree)

	ok := assert.Equal(t, wantTree, gotTree)
	if !ok {
		fmt.Println("Want tree:")
		printTree(wantTree)
		fmt.Println("Got tree:")
		printTree(gotTree)
	}
	return ok
}

func AssertPagesEqualWithLinks(t *testing.T, want, got []*model.Block, idsMap map[string]string) bool {
	wantTree := BuildAST(want)
	gotTree := BuildAST(got)

	dropBlockIDs(wantTree)
	dropBlockIDs(gotTree)

	remapLinks(gotTree, idsMap)
	return assert.Equal(t, wantTree, gotTree)
}

func printTree(root *Block) {
	b := &strings.Builder{}
	renderNode(b, root, 0)
	fmt.Println(b.String())
}

func renderNode(b *strings.Builder, node *Block, lvl int) {
	b.WriteString(strings.Repeat("  ", lvl))
	b.WriteString(node.String())
	b.WriteString("\n")
	for _, c := range node.children {
		renderNode(b, c, lvl+1)
	}
}
