//go:build cgo

package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/goccy/go-graphviz"
	"github.com/gogo/protobuf/jsonpb"
	"github.com/gogo/protobuf/proto"

	"github.com/anyproto/anytype-heart/core/block/editor/state"
	"github.com/anyproto/anytype-heart/core/debug/exporter"
	"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
	"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
	"github.com/anyproto/anytype-heart/util/pbtypes"
)

var (
	file        = flag.String("f", "", "path to debug file")
	fromRoot    = flag.Bool("r", false, "build from root of the tree")
	makeJson    = flag.Bool("j", false, "generate json file")
	makeTree    = flag.Bool("t", false, "generate graphviz file")
	printState  = flag.Bool("s", false, "print result state debug")
	changeIdx   = flag.Int("c", -1, "build tree before given index and print change")
	objectStore = flag.Bool("o", false, "show object store info")
	fileHashes  = flag.Bool("h", false, "show file hashes in state")
)

func main() {
	flag.Parse()
	if *file == "" {
		flag.PrintDefaults()
		return
	}
	fmt.Println("opening file...")
	var (
		st  = time.Now()
		ctx = context.Background()
	)
	res, err := exporter.ImportStorage(ctx, *file)
	if err != nil {
		log.Fatal("can't import the tree:", err)
	}
	defer res.Store.Close()
	objectTree, err := res.CreateReadableTree(*fromRoot, "")
	if err != nil {
		log.Fatal("can't create readable tree:", err)
	}
	fmt.Printf("open archive done in %.1fs\n", time.Since(st).Seconds())
	importer := exporter.NewTreeImporter(objectTree)
	if *makeJson {
		treeJson, err := importer.Json()
		if err != nil {
			log.Fatal("can't build json:", err)
		}
		res, err := json.MarshalIndent(treeJson, "", "  ")
		if err != nil {
			log.Fatal("can't marshall json:", err)
		}
		tf, err := ioutil.TempFile("", "tree_*.json")
		if err != nil {
			log.Fatal("can't create temp file:", err)
		}
		fmt.Println("tree json file:", tf.Name())
		tf.Write(res)
		tf.Close()
	}

	if *changeIdx != -1 {
		ch, err := importer.ChangeAt(*changeIdx)
		if err != nil {
			log.Fatal("can't get the change in tree: ", err)
		}
		fmt.Println("Change:")
		fmt.Println(pbtypes.Sprint(ch.Model))
		objectTree, err = res.CreateReadableTree(*fromRoot, ch.Id)
		if err != nil {
			log.Fatal("can't create readable tree:", err)
		}
		importer = exporter.NewTreeImporter(objectTree)
	}
	ot := importer.ObjectTree()
	di, err := ot.Debug(state.ChangeParser{})
	if err != nil {
		log.Fatal("can't get debug info from tree", err)
	}
	fmt.Printf("Tree root:\t%s\nTree len:\t%d\nTree heads:\t%s\n",
		ot.Root().Id,
		di.TreeLen,
		strings.Join(di.Heads, ","))

	if *printState {
		fmt.Println("Building state...")
		stt := time.Now()
		s, err := importer.State()
		if err != nil {
			log.Fatal("can't build state:", err)
		}
		dur := time.Since(stt)
		fmt.Println(s.StringDebug())

		payload := &model.ObjectChangePayload{}
		err = proto.Unmarshal(ot.ChangeInfo().ChangePayload, payload)
		if err != nil {
			return
		}
		sbt := smartblock.SmartBlockType(payload.SmartBlockType)
		fmt.Printf("Smarblock type:\t%v\n", sbt.ToProto())
		if *fileHashes {
			fmt.Println("File keys:")
			for _, fk := range s.GetAndUnsetFileKeys() {
				fmt.Printf("\t%s: %d\n", fk.Hash, len(fk.Keys))
			}
		}
		fmt.Println("state building time:", dur)
	}

	if *objectStore {
		fmt.Println("fetch object store info..")
		f, err := os.Open(filepath.Join(res.FolderPath, "localstore.json"))
		if err != nil {
			log.Fatal("can't open objectStore info:", err)
		}
		info := &model.ObjectInfo{}
		if err = jsonpb.Unmarshal(f, info); err != nil {
			log.Fatal("can't unmarshal objectStore info:", err)
		}
		defer f.Close()
		fmt.Println(pbtypes.Sprint(info))
	}

	if *makeTree {
		gvo, err := graphviz.ParseBytes([]byte(di.Graphviz))
		if err != nil {
			log.Fatal("can't open graphviz data:", err)
		}
		tf, err := ioutil.TempFile("", "tree_*.svg")
		if err != nil {
			log.Fatal("can't create temp file:", err)
		}
		ctx := context.Background()
		g, err := graphviz.New(ctx)
		if err != nil {
			log.Fatal("can't open graphviz:", err)
		}
		err = g.Render(ctx, gvo, graphviz.SVG, tf)
		if err != nil {
			log.Fatal("can't render graphviz:", err)
		}
		fmt.Println("tree file:", tf.Name())
		tf.Close()
		open(tf.Name())
	}
}

func open(path string) {
	var err error

	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", path).Start()
	case "windows":
		err = exec.Command("rundll32", "rl.dll,FileProtocolHandler", path).Start()
	case "darwin":
		err = exec.Command("open", path).Start()
	default:
		err = fmt.Errorf("unsupported platform")
	}
	if err != nil {
		log.Fatal(err)
	}
}
