// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2

package blockchain

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"path/filepath"
	"sync"
	"syscall"

	corelog "dappco.re/go/core/log"

	"dappco.re/go/core/blockchain/chain"
	"dappco.re/go/core/process"
	store "dappco.re/go/core/store"
	"github.com/spf13/cobra"
)

// newChainSyncCommand builds the `chain sync` command family.
//
// Example:
//
//	chain sync
//	chain sync --daemon
//	chain sync --stop
//
// It keeps the foreground and daemon modes behind a predictable command path.
func newChainSyncCommand(chainDataDir, seedPeerAddress *string, useTestnet *bool) *cobra.Command {
	var (
		daemon bool
		stop   bool
	)

	cmd := &cobra.Command{
		Use:   "sync",
		Short: "Headless P2P chain sync",
		Long:  "Sync the blockchain from P2P peers without the TUI explorer.",
		Args:  cobra.NoArgs,
		PreRunE: func(cmd *cobra.Command, args []string) error {
			if daemon && stop {
				return corelog.E("newChainSyncCommand", "flags --daemon and --stop cannot be combined", nil)
			}
			return validateChainOptions(*chainDataDir, *seedPeerAddress)
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			if stop {
				return stopChainSyncDaemon(*chainDataDir)
			}
			if daemon {
				return runChainSyncDaemon(*chainDataDir, *seedPeerAddress, *useTestnet)
			}
			return runChainSyncForeground(*chainDataDir, *seedPeerAddress, *useTestnet)
		},
	}

	cmd.Flags().BoolVar(&daemon, "daemon", false, "run as background daemon")
	cmd.Flags().BoolVar(&stop, "stop", false, "stop a running sync daemon")

	return cmd
}

func runChainSyncForeground(chainDataDir, seedPeerAddress string, useTestnet bool) error {
	if err := ensureChainDataDirExists(chainDataDir); err != nil {
		return err
	}

	dbPath := filepath.Join(chainDataDir, "chain.db")
	chainStore, err := store.New(dbPath)
	if err != nil {
		return corelog.E("runChainSyncForeground", "open store", err)
	}
	defer chainStore.Close()

	blockchain := chain.New(chainStore)
	chainConfig, hardForks, resolvedSeed := chainConfigForSeed(useTestnet, seedPeerAddress)

	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()

	corelog.Info("starting headless P2P sync", "data_dir", chainDataDir, "seed", resolvedSeed, "testnet", useTestnet)
	runChainSyncLoop(ctx, blockchain, &chainConfig, hardForks, resolvedSeed)
	corelog.Info("headless P2P sync stopped", "data_dir", chainDataDir)
	return nil
}

func runChainSyncDaemon(chainDataDir, seedPeerAddress string, useTestnet bool) error {
	if err := ensureChainDataDirExists(chainDataDir); err != nil {
		return err
	}

	pidFile := filepath.Join(chainDataDir, "sync.pid")

	daemon := process.NewDaemon(process.DaemonOptions{
		PIDFile:  pidFile,
		Registry: process.DefaultRegistry(),
		RegistryEntry: process.DaemonEntry{
			Code:   "dappco.re/go/core/blockchain",
			Daemon: "sync",
		},
	})

	if err := daemon.Start(); err != nil {
		return corelog.E("runChainSyncDaemon", "daemon start", err)
	}

	dbPath := filepath.Join(chainDataDir, "chain.db")
	chainStore, err := store.New(dbPath)
	if err != nil {
		_ = daemon.Stop()
		return corelog.E("runChainSyncDaemon", "open store", err)
	}
	defer chainStore.Close()

	blockchain := chain.New(chainStore)
	chainConfig, hardForks, resolvedSeed := chainConfigForSeed(useTestnet, seedPeerAddress)

	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()

	daemon.SetReady(true)
	corelog.Info("sync daemon started", "data_dir", chainDataDir, "seed", resolvedSeed, "testnet", useTestnet)

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		runChainSyncLoop(ctx, blockchain, &chainConfig, hardForks, resolvedSeed)
	}()

	err = daemon.Run(ctx)
	wg.Wait() // Wait for the sync loop to finish before closing the store.
	return err
}

func stopChainSyncDaemon(chainDataDir string) error {
	pidFile := filepath.Join(chainDataDir, "sync.pid")
	pid, running := process.ReadPID(pidFile)
	if pid == 0 || !running {
		return corelog.E("stopChainSyncDaemon", "no running sync daemon found", nil)
	}

	processHandle, err := os.FindProcess(pid)
	if err != nil {
		return corelog.E("stopChainSyncDaemon", fmt.Sprintf("find process %d", pid), err)
	}

	if err := processHandle.Signal(syscall.SIGTERM); err != nil {
		return corelog.E("stopChainSyncDaemon", fmt.Sprintf("signal process %d", pid), err)
	}

	corelog.Info("sent SIGTERM to sync daemon", "pid", pid)
	return nil
}
