// Copyright 2025 The XDC Authors
// This file is part of the XDC library.
//
// The XDC library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The XDC library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the XDC library. If not, see <http://www.gnu.org/licenses/>.

// Package downloader contains the XDC checkpoint sync extraction.
// This file isolates checkpoint-specific logic from the main downloader
// to improve maintainability and upstream alignment.
package downloader

import (
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/rawdb"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/log"
)

// insertCheckpointAnchor fetches the configured cutoff block from the peer and
// installs it as a trusted anchor in the local chain.
func (d *Downloader) insertCheckpointAnchor(p *peerConnection) error {
	if d.chainCutoffNumber == 0 {
		return nil
	}
	if d.chainCutoffHash != (common.Hash{}) && d.blockchain.HasHeader(d.chainCutoffHash, d.chainCutoffNumber) {
		log.Debug("XDC sync: checkpoint anchor already present", "number", d.chainCutoffNumber)
		return nil
	}

	log.Info("XDC sync: fetching checkpoint anchor", "number", d.chainCutoffNumber)
	headers, err := d.requestHeadersByNumberXDC(p, d.chainCutoffNumber, 1, 0, false)
	if err != nil {
		return fmt.Errorf("fetch checkpoint header: %w", err)
	}
	if len(headers) != 1 {
		return fmt.Errorf("checkpoint fetch returned %d headers, expected 1", len(headers))
	}
	cp := headers[0]
	if cp.Number == nil || cp.Number.Uint64() != d.chainCutoffNumber {
		return fmt.Errorf("checkpoint fetch returned wrong number: got %v want %d", cp.Number, d.chainCutoffNumber)
	}
	if d.chainCutoffHash != (common.Hash{}) && cp.Hash() != d.chainCutoffHash {
		return fmt.Errorf("checkpoint hash mismatch: got %x want %x", cp.Hash(), d.chainCutoffHash)
	}

	if !d.blockchain.HasState(cp.Root) {
		log.Warn("XDC sync: state trie not available at checkpoint",
			"checkpoint", d.chainCutoffNumber, "stateRoot", cp.Root.Hex()[:16])
		d.blockchain.SetCheckpointSyncNoState(true)
		rawdb.WriteCheckpointSyncNoState(d.stateDB, true)
		log.Info("XDC sync: enabled checkpoint sync without state mode")
	}

	if _, err := d.blockchain.InsertHeadersBeforeCutoff([]*types.Header{cp}); err != nil {
		log.Error("XDC sync: InsertHeadersBeforeCutoff failed", "err", err, "checkpoint", d.chainCutoffNumber)
		return fmt.Errorf("insert checkpoint header: %w", err)
	}
	log.Info("XDC sync: checkpoint anchor inserted", "number", cp.Number, "hash", cp.Hash().Hex()[:16])

	checkpointBlock := types.NewBlockWithHeader(cp)
	rawdb.WriteBody(d.stateDB, cp.Hash(), d.chainCutoffNumber, checkpointBlock.Body())
	rawdb.WriteHeader(d.stateDB, cp)
	rawdb.WriteCanonicalHash(d.stateDB, cp.Hash(), d.chainCutoffNumber)
	rawdb.WriteHeadHeaderHash(d.stateDB, cp.Hash())
	log.Info("XDC sync: checkpoint block written to live DB", "number", cp.Number, "hash", cp.Hash().Hex()[:16])

	d.blockchain.SetCheckpointPrunePoint(d.chainCutoffNumber, cp.Hash())

	meta := &rawdb.CheckpointSyncMetadata{
		Number: d.chainCutoffNumber,
		Hash:   cp.Hash(),
		Root:   cp.Root,
	}
	rawdb.WriteCheckpointSyncMetadata(d.stateDB, meta)

	if db, ok := d.stateDB.(interface{ Sync() error }); ok {
		if err := db.Sync(); err != nil {
			log.Warn("XDC sync: failed to sync checkpoint metadata to disk", "err", err)
		} else {
			log.Debug("XDC sync: checkpoint metadata synced to disk")
		}
	}
	return nil
}

// checkpointSyncReady returns true if checkpoint sync is configured and ready.
func (d *Downloader) checkpointSyncReady() bool {
	return d.chainCutoffNumber != 0 && d.chainCutoffHash != (common.Hash{})
}

// clearCheckpointMetadata removes checkpoint sync state after successful sync.
func (d *Downloader) clearCheckpointMetadata(latest uint64) {
	if d.chainCutoffNumber != 0 && latest > d.chainCutoffNumber+100000 {
		rawdb.DeleteCheckpointSyncMetadata(d.stateDB)
		d.chainCutoffNumber = 0
		d.chainCutoffHash = common.Hash{}
		log.Info("Checkpoint sync metadata cleared", "latest", latest)
	}
}
