package types

import (
	"fmt"
	"math"

	agglayertypes "github.com/agglayer/aggkit/agglayer/types"
	"github.com/agglayer/aggkit/bridgesync"
	aggkitcommon "github.com/agglayer/aggkit/common"
	"github.com/ethereum/go-ethereum/common"
)

const claimSizeFactor = 200 // Size factor for claims in bytes

// CertificatePreBuildParams is a struct that holds the parameters to pre-build a certificate
// basically it's used for generate CertificateBuildParams that add bridges
type CertificatePreBuildParams struct {
	BlockRange          BlockRange
	RetryCount          int
	CertificateType     CertificateType
	LastSentCertificate *CertificateHeader
	L1InfoTreeToProve   *CertificateL1InfoTreeData
	CreatedAt           uint32
}

func (c *CertificatePreBuildParams) String() string {
	if c == nil {
		return "CertificatePreBuildParams is nil"
	}
	return fmt.Sprintf("Type: %s BlockRange: %s,  RetryCount: %d, L1InfoTreeToProve:%s, CreatedAt:%d",
		c.CertificateType, c.BlockRange.String(), c.RetryCount, c.L1InfoTreeToProve.String(), c.CreatedAt)
}

// CertificateL1InfoTreeData is a struct that holds the L1 info tree root and leaf count
// that is used to prove the L1 info tree in the certificate
type CertificateL1InfoTreeData struct {
	L1InfoTreeRootToProve common.Hash
	L1InfoTreeLeafCount   uint32
}

func (c *CertificateL1InfoTreeData) String() string {
	if c == nil {
		return "CertificateL1InfoTreeData is nil"
	}
	return fmt.Sprintf("CertificateL1InfoTreeData{L1InfoTreeRootFromWhichToProve: %s, L1InfoTreeLeafCount: %d}",
		c.L1InfoTreeRootToProve.Hex(), c.L1InfoTreeLeafCount)
}

// CertificateBuildParams is a struct that holds the parameters to build a certificate
type CertificateBuildParams struct {
	FromBlock                      uint64
	ToBlock                        uint64
	Bridges                        []bridgesync.Bridge
	Claims                         []bridgesync.Claim
	CreatedAt                      uint32
	RetryCount                     int
	LastSentCertificate            *CertificateHeader
	L1InfoTreeRootFromWhichToProve common.Hash
	L1InfoTreeLeafCount            uint32
	AggchainProof                  *AggchainProof
	CertificateType                CertificateType
	ExtraData                      string
}

func (c *CertificateBuildParams) String() string {
	return fmt.Sprintf("Type: %s FromBlock: %d, ToBlock: %d, numBridges: %d, numClaims: %d, createdAt: %d",
		c.CertificateType, c.FromBlock, c.ToBlock, c.NumberOfBridges(), c.NumberOfClaims(), c.CreatedAt)
}

// Range create a new CertificateBuildParams with the given range
func (c *CertificateBuildParams) Range(fromBlock, toBlock uint64) (*CertificateBuildParams, error) {
	if c.FromBlock == fromBlock && c.ToBlock == toBlock {
		return c, nil
	}
	if c.FromBlock > fromBlock || c.ToBlock < toBlock {
		return nil, fmt.Errorf("invalid range. FromBlock %d and ToBlock %d are not within "+
			"the certificate range FromBlock %d and ToBlock %d",
			fromBlock, toBlock, c.FromBlock, c.ToBlock)
	}

	if fromBlock > toBlock {
		return nil, fmt.Errorf("invalid range. FromBlock %d is greater than toBlock %d", fromBlock, toBlock)
	}

	span := toBlock - fromBlock + 1
	fullSpan := c.ToBlock - c.FromBlock + 1

	newCert := &CertificateBuildParams{
		FromBlock: fromBlock,
		ToBlock:   toBlock,
		Bridges: make([]bridgesync.Bridge, 0,
			aggkitcommon.EstimateSliceCapacity(len(c.Bridges), span, fullSpan)),
		Claims: make([]bridgesync.Claim, 0,
			aggkitcommon.EstimateSliceCapacity(len(c.Claims), span, fullSpan)),
		CreatedAt:                      c.CreatedAt,
		RetryCount:                     c.RetryCount,
		LastSentCertificate:            c.LastSentCertificate,
		AggchainProof:                  c.AggchainProof,
		L1InfoTreeRootFromWhichToProve: c.L1InfoTreeRootFromWhichToProve,
		L1InfoTreeLeafCount:            c.L1InfoTreeLeafCount,
		CertificateType:                c.CertificateType,
	}

	for _, bridge := range c.Bridges {
		if bridge.BlockNum >= fromBlock && bridge.BlockNum <= toBlock {
			newCert.Bridges = append(newCert.Bridges, bridge)
		}
	}

	for _, claim := range c.Claims {
		if claim.BlockNum >= fromBlock && claim.BlockNum <= toBlock {
			newCert.Claims = append(newCert.Claims, claim)
		}
	}
	return newCert, nil
}

// NumberOfBridges returns the number of bridges in the certificate
func (c *CertificateBuildParams) NumberOfBridges() int {
	if c == nil {
		return 0
	}
	return len(c.Bridges)
}

// NumberOfClaims returns the number of claims in the certificate
func (c *CertificateBuildParams) NumberOfClaims() int {
	if c == nil {
		return 0
	}
	return len(c.Claims)
}

// NumberOfBlocks returns the number of blocks in the certificate
func (c *CertificateBuildParams) NumberOfBlocks() int {
	if c == nil {
		return 0
	}
	numBlocks := c.ToBlock - c.FromBlock + 1
	// Check if result would overflow when converting to int
	if numBlocks > uint64(math.MaxInt) {
		return math.MaxInt
	}

	return int(numBlocks)
}

// EstimatedSize returns the estimated size of the certificate
func (c *CertificateBuildParams) EstimatedSize() uint {
	if c == nil {
		return 0
	}
	sizeBridges := float64(0)
	for _, bridge := range c.Bridges {
		sizeBridges += agglayertypes.EstimatedBridgeExitSize
		sizeBridges += float64(len(bridge.Metadata))
	}

	sizeClaims := float64(0)
	for _, claim := range c.Claims {
		sizeClaims += agglayertypes.EstimatedImportedBridgeExitSize
		sizeClaims += float64(len(claim.Metadata))
	}

	sizeAggchainData := float64(0)
	switch c.CertificateType {
	case CertificateTypeFEP:
		sizeAggchainData += agglayertypes.EstimatedAggchainProofSize
		sizeAggchainData += float64(len(c.Claims) * claimSizeFactor) // for each claim the proof gets bigger by some size
	default:
		sizeAggchainData += agglayertypes.EstimatedAggchainSignatureSize
	}

	return uint(sizeBridges + sizeClaims + sizeAggchainData)
}

// IsEmpty returns true if the certificate is empty
func (c *CertificateBuildParams) IsEmpty() bool {
	return c.NumberOfBridges() == 0 && c.NumberOfClaims() == 0
}

// IsARetry returns true if the certificate is a retry
func (c *CertificateBuildParams) IsARetry() bool {
	return c != nil && c.RetryCount > 0 && c.LastSentCertificate != nil
}

// MaxDepoitCount returns the maximum deposit count in the certificate
func (c *CertificateBuildParams) MaxDepositCount() uint32 {
	if c == nil || c.NumberOfBridges() == 0 {
		return 0
	}
	return c.Bridges[len(c.Bridges)-1].DepositCount
}
