// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package cmd

import (
	"context"
	"fmt"

	"code.forgejo.org/f3/gof3/v3/logger"
	"code.forgejo.org/f3/gof3/v3/options"
	"code.forgejo.org/f3/gof3/v3/path"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	"code.forgejo.org/f3/gof3/v3/util"

	"github.com/urfave/cli/v3"
)

var (
	directionFrom = "from"
	flagFrom      = "--" + directionFrom
	directionTo   = "to"
	flagTo        = "--" + directionTo
)

func BuildForgePrefix(prefix, forge string) string {
	return prefix + "-" + forge
}

func FlagsToTree(ctx context.Context, c *cli.Command, direction string) generic.TreeInterface {
	forgeType := c.String(ForgeTypeOption(direction))
	opts := options.GetFactory(forgeType)()
	opts.(options.LoggerInterface).SetLogger(logger.ContextGetLogger(ctx))
	if o, ok := opts.(options.CLIInterface); ok {
		o.FromFlags(ctx, c, BuildForgePrefix(direction, forgeType))
	} else {
		panic("not implemented")
	}
	return generic.GetFactory("f3")(ctx, opts)
}

func CreateCmdMirror() *cli.Command {
	flags := make([]cli.Flag, 0, 10)
	for _, direction := range []string{"from", "to"} {
		flags = append(flags, GetFlagsCommon(direction, "common")...)
		for name, factory := range options.GetFactories() {
			if opts, ok := factory().(options.CLIInterface); ok {
				flags = append(flags, opts.GetFlags(BuildForgePrefix(direction, name), name)...)
			}
		}
	}

	flags = func(flags []cli.Flag) []cli.Flag {
		dedup := make([]cli.Flag, 0, 10)
		names := make(map[string]any, 10)
	flagLoop:
		for _, flag := range flags {
			for _, name := range flag.Names() {
				_, found := names[name]
				if found {
					continue flagLoop
				}
			}
			dedup = append(dedup, flag)
			for _, name := range flag.Names() {
				names[name] = nil
			}
		}
		return dedup
	}(flags)

	return &cli.Command{
		Name:        "mirror",
		Usage:       "Mirror",
		Description: "Mirror",
		Action: func(ctx context.Context, c *cli.Command) error {
			return util.PanicToError(func() { runMirror(ctx, c) })
		},
		Flags: flags,
	}
}

func runMirror(ctx context.Context, c *cli.Command) {
	from := FlagsToTree(ctx, c, directionFrom)
	to := FlagsToTree(ctx, c, directionTo)
	fromPathString := c.String(BuildForgePrefix(directionFrom, "path"))
	fromPath := generic.NewPathFromString(fromPathString)
	toPathString := c.String(BuildForgePrefix(directionTo, "path"))
	toPath := generic.NewPathFromString(toPathString)

	log := from.GetLogger()

	fromURL := "(unset)"
	if url, ok := from.GetOptions().(options.URLInterface); ok {
		fromURL = url.GetURL()
	}
	toURL := "(unset)"
	if url, ok := to.GetOptions().(options.URLInterface); ok {
		toURL = url.GetURL()
	}
	log.Info("mirror %s (%s at %s) to %s (%s at %s)",
		fromPath, c.String(ForgeTypeOption(directionFrom)), fromURL,
		toPath, c.String(ForgeTypeOption(directionTo)), toURL,
	)

	log.Debug("read %s from %T", fromPath, from)
	var fromNode generic.NodeInterface
	fromNode = generic.NilNode
	walkAndGet := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
		node.WalkAndGet(ctx, parent, generic.NewWalkOptions(nil))
		fromNode = node
	}
	from.ApplyAndGet(ctx, fromPath, generic.NewApplyOptions(walkAndGet))
	if fromNode == generic.NilNode {
		panic(fmt.Errorf("from %s not found", fromPath))
	}
	from.Debug("copy %s from %T to %T", fromPath, from, to)

	if toPathString == "" {
		generic.TreeMirror(ctx, from, to, fromPath, generic.NewMirrorOptions())
	} else {
		toNode := to.FindAndGet(ctx, toPath)
		if toNode == generic.NilNode {
			panic(fmt.Errorf("to %s not found", toPath))
		}
		generic.NodeMirror(ctx, fromNode, toNode, generic.NewMirrorOptions())
	}
}
