// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package quasar

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/luxfi/consensus/protocol/quasar"
	"github.com/luxfi/ids"
	"github.com/luxfi/log"
)

// Quasar is the gravitational center of Lux consensus.
// It binds P-Chain (BLS signatures) and Q-Chain (Corona post-quantum threshold)
// into unified hybrid finality across all Lux networks.
//
// Architecture:
//   ALL validators have BOTH keypairs:
//   - BLS keypair → aggregate signatures (classical, fast)
//   - Corona keypair → threshold signatures (post-quantum, 2-round)
//
//   Both signature paths run IN PARALLEL:
//
//   Block arrives
//       │
//       ├─────────────────────────────────────────┐
//       │                                         │
//       ▼                                         ▼
//   BLS PATH (fast)                    CORONA PATH (quantum-safe)
//   ────────────────                   ─────────────────────────────
//   All validators sign                Round 1: All validators
//   with BLS keys                      generate commitments
//       │                                         │
//       ▼                                         ▼
//   Aggregate into                     Round 2: All validators
//   single 96-byte sig                 compute partial signatures
//       │                                         │
//       └─────────────────┬───────────────────────┘
//                         │
//                         ▼
//                  Finalize: combine into
//                  threshold signature
//                         │
//                         ▼
//               ┌─────────────────┐
//               │  HYBRID PROOF   │
//               │ BLS Aggregate   │ ← 96 bytes (2/3+ validators)
//               │ Corona Thresh │ ← ~KB (t-of-n threshold)
//               └─────────────────┘
//                         │
//                         ▼
//                QUANTUM FINALITY
//
// The quasar ensures blocks achieve finality only when BOTH complete:
// 1. 2/3+ validator weight signed via BLS (fast, classical)
// 2. t-of-n validators completed Corona threshold (post-quantum secure)

var (
	ErrQuasarNotStarted    = errors.New("quasar not started")
	ErrPChainNotConnected  = errors.New("P-Chain not connected")
	ErrQChainNotConnected  = errors.New("Q-Chain not connected")
	ErrCoronaNotConnected  = errors.New("Corona coordinator not connected")
	ErrInsufficientWeight  = errors.New("insufficient validator weight")
	ErrInsufficientSigners = errors.New("insufficient Corona signers")
	ErrFinalityFailed      = errors.New("hybrid finality verification failed")
	ErrBLSFailed           = errors.New("BLS aggregation failed")
	ErrCoronaFailed        = errors.New("Corona threshold signing failed")
)

// PChainProvider provides P-Chain state and finality events
type PChainProvider interface {
	GetFinalizedHeight() uint64
	GetValidators(height uint64) ([]ValidatorState, error)
	SubscribeFinality() <-chan FinalityEvent
}

// QuantumSignerFallback provides fallback single-signer quantum signatures
type QuantumSignerFallback interface {
	SignMessage(msg []byte) ([]byte, error)
}

// ValidatorState represents a validator's current state
// Each validator has BOTH BLS and Corona keys
type ValidatorState struct {
	NodeID    ids.NodeID
	Weight    uint64
	BLSPubKey []byte // BLS public key for aggregate signatures
	CoronaKey []byte // Corona public key share for threshold sigs
	Active    bool
}

// FinalityEvent represents a P-Chain finality event
type FinalityEvent struct {
	Height     uint64
	BlockID    ids.ID
	Validators []ValidatorState
	Timestamp  time.Time
}

// QuantumFinality represents a block that achieved hybrid quantum finality
type QuantumFinality struct {
	BlockID       ids.ID
	PChainHeight  uint64
	QChainHeight  uint64
	BLSProof      []byte       // Aggregated BLS signature (96 bytes)
	CoronaProof   []byte       // Serialized Corona threshold signature
	SignerBitset  []byte       // Which validators signed BLS
	CoronaSigners []ids.NodeID // Which validators participated in Corona
	TotalWeight   uint64
	SignerWeight  uint64
	BLSLatency    time.Duration
	CoronaLatency time.Duration
	Timestamp     time.Time
}

// Quasar binds P-Chain and Q-Chain consensus into hybrid quantum finality
type Quasar struct {
	mu sync.RWMutex

	log  log.Logger
	core *quasar.Quasar

	// Chain connections
	pChain          PChainProvider
	quantumFallback QuantumSignerFallback

	// Corona threshold coordinator
	corona *CoronaCoordinator

	// State
	pHeight   uint64
	qHeight   uint64
	finalized map[ids.ID]*QuantumFinality

	// Configuration
	threshold    int    // Corona threshold (t in t-of-n)
	quorumNum    uint64 // BLS quorum numerator
	quorumDen    uint64 // BLS quorum denominator
	maxFinalized int    // max finalized entries before pruning

	// Channels
	finalityCh chan *QuantumFinality
	stopCh     chan struct{}
	running    bool
}

// NewQuasar creates a new Quasar consensus hub
func NewQuasar(log log.Logger, threshold int, quorumNum, quorumDen uint64) (*Quasar, error) {
	core, err := quasar.NewQuasar(threshold)
	if err != nil {
		return nil, fmt.Errorf("failed to create quasar core: %w", err)
	}

	return &Quasar{
		log:          log,
		core:         core,
		threshold:    threshold,
		quorumNum:    quorumNum,
		quorumDen:    quorumDen,
		finalized:    make(map[ids.ID]*QuantumFinality),
		maxFinalized: 10000,
		finalityCh:   make(chan *QuantumFinality, 100),
		stopCh:       make(chan struct{}),
	}, nil
}

// ConnectPChain connects the P-Chain finality provider
func (q *Quasar) ConnectPChain(p PChainProvider) {
	q.mu.Lock()
	defer q.mu.Unlock()

	q.pChain = p
	if p != nil {
		q.pHeight = p.GetFinalizedHeight()
	}

	q.log.Info("quasar: P-Chain connected", "height", q.pHeight)
}

// ConnectQuantumFallback connects the quantum signer fallback
func (q *Quasar) ConnectQuantumFallback(f QuantumSignerFallback) {
	q.mu.Lock()
	defer q.mu.Unlock()

	q.quantumFallback = f
	q.log.Info("quasar: quantum fallback connected")
}

// ConnectCorona connects the Corona threshold coordinator
func (q *Quasar) ConnectCorona(rc *CoronaCoordinator) {
	q.mu.Lock()
	defer q.mu.Unlock()

	q.corona = rc
	q.log.Info("quasar: Corona coordinator connected")
}

// InitializeCorona initializes the Corona coordinator with validators
func (q *Quasar) InitializeCorona(validators []ids.NodeID) error {
	q.mu.Lock()
	defer q.mu.Unlock()

	if q.corona == nil {
		// Create coordinator if not provided
		numParties := len(validators)
		threshold := (numParties * 2 / 3) + 1 // 2/3 + 1 threshold
		if threshold < 2 {
			threshold = 2
		}

		rc, err := NewCoronaCoordinator(q.log, CoronaConfig{
			NumParties: numParties,
			Threshold:  threshold,
		})
		if err != nil {
			return fmt.Errorf("failed to create Corona coordinator: %w", err)
		}
		q.corona = rc
	}

	if err := q.corona.Initialize(validators); err != nil {
		return fmt.Errorf("failed to initialize Corona: %w", err)
	}

	q.log.Info("quasar: Corona initialized",
		"validators", len(validators),
		"threshold", q.corona.Stats().Threshold,
	)

	return nil
}

// Start begins the quasar consensus loop
func (q *Quasar) Start(ctx context.Context) error {
	q.mu.Lock()
	if q.pChain == nil {
		q.mu.Unlock()
		return ErrPChainNotConnected
	}
	if q.quantumFallback == nil {
		q.mu.Unlock()
		return ErrQChainNotConnected
	}
	q.running = true
	q.mu.Unlock()

	// Subscribe to P-Chain finality
	sub := q.pChain.SubscribeFinality()
	go q.run(ctx, sub)

	q.log.Info("quasar: started")
	return nil
}

// Stop halts the quasar
func (q *Quasar) Stop() {
	q.mu.Lock()
	if q.running {
		close(q.stopCh)
		q.running = false
	}
	q.mu.Unlock()
	q.log.Info("quasar: stopped")
}

// run is the main finality loop.
// Bounded by ctx.Done() and q.stopCh — exits when either fires.
// The goroutine is started in Start() and guaranteed to terminate
// when Stop() closes stopCh or the parent context is cancelled.
func (q *Quasar) run(ctx context.Context, sub <-chan FinalityEvent) {
	for {
		select {
		case <-ctx.Done():
			return
		case <-q.stopCh:
			return
		case event := <-sub:
			if err := q.processFinality(ctx, event); err != nil {
				q.log.Error("quasar: finality failed",
					"height", event.Height,
					"error", err,
				)
			}
		}
	}
}

// processFinality processes a P-Chain finality event into hybrid finality
// Both BLS and Corona paths run IN PARALLEL
func (q *Quasar) processFinality(ctx context.Context, event FinalityEvent) error {
	q.mu.Lock()
	defer q.mu.Unlock()

	// Sync validators to quasar core
	for _, v := range event.Validators {
		if v.Active {
			_, _ = q.core.AddValidator(v.NodeID.String(), v.Weight)
		}
	}

	// Create finality message
	msg := q.createMessage(event)
	msgStr := string(msg) // Corona uses string message

	// Run BLS and Corona IN PARALLEL
	var blsProof, signerBitset []byte
	var signerWeight uint64
	var coronaSig Signature
	var blsLatency, coronaLatency time.Duration
	var blsErr, coronaErr error
	var wg sync.WaitGroup

	// BLS path
	wg.Add(1)
	go func() {
		defer wg.Done()
		start := time.Now()
		blsProof, signerBitset, signerWeight, blsErr = q.collectBLS(event, msg)
		blsLatency = time.Since(start)
	}()

	// Corona path - REQUIRED for Q-Chain validator consensus.
	// No fallback mode: if Corona coordinator is not initialized,
	// finality MUST fail to prevent accepting BLS-only proofs.
	wg.Add(1)
	go func() {
		defer wg.Done()
		if q.corona == nil || !q.corona.IsInitialized() {
			// STRICT: No fallback allowed for validators.
			// RT signatures are REQUIRED for quantum-safe consensus.
			coronaErr = ErrCoronaNotConnected
			return
		}
		// Full threshold signing
		start := time.Now()
		coronaSig, coronaErr = q.collectCorona(msgStr)
		coronaLatency = time.Since(start)
	}()

	wg.Wait()

	// Check BLS result
	if blsErr != nil {
		return fmt.Errorf("BLS collection: %w", blsErr)
	}

	// Check Corona result
	if coronaErr != nil {
		return fmt.Errorf("Corona threshold: %w", coronaErr)
	}

	// Check quorum
	totalWeight := q.totalWeight(event.Validators)
	if !q.checkQuorum(signerWeight, totalWeight) {
		return ErrInsufficientWeight
	}

	// Record finality
	q.qHeight++
	var coronaSigners []ids.NodeID
	var coronaProof []byte
	if coronaSig != nil {
		coronaSigners = coronaSig.Signers()
		coronaProof = coronaSig.Bytes()
	}
	finality := &QuantumFinality{
		BlockID:       event.BlockID,
		PChainHeight:  event.Height,
		QChainHeight:  q.qHeight,
		BLSProof:      blsProof,
		CoronaProof:   coronaProof,
		SignerBitset:  signerBitset,
		CoronaSigners: coronaSigners,
		TotalWeight:   totalWeight,
		SignerWeight:  signerWeight,
		BLSLatency:    blsLatency,
		CoronaLatency: coronaLatency,
		Timestamp:     time.Now(),
	}

	q.finalized[event.BlockID] = finality
	q.pHeight = event.Height

	// Prune old finality entries to bound memory
	if q.maxFinalized > 0 && len(q.finalized) > q.maxFinalized {
		cutoff := q.qHeight - uint64(q.maxFinalized)
		for id, f := range q.finalized {
			if f.QChainHeight < cutoff {
				delete(q.finalized, id)
			}
		}
	}

	// Emit
	select {
	case q.finalityCh <- finality:
	default:
	}

	q.log.Info("quasar: hybrid finality achieved",
		"block", event.BlockID,
		"pHeight", event.Height,
		"qHeight", q.qHeight,
		"weight", fmt.Sprintf("%d/%d", signerWeight, totalWeight),
		"blsLatency", blsLatency,
		"coronaLatency", coronaLatency,
		"coronaSigners", len(coronaSigners),
	)

	return nil
}

// createMessage creates the finality message to sign
func (q *Quasar) createMessage(event FinalityEvent) []byte {
	msg := make([]byte, 48) // 32 (blockID) + 8 (height) + 8 (timestamp)
	copy(msg[:32], event.BlockID[:])
	putUint64BE(msg[32:40], event.Height)
	putUint64BE(msg[40:48], uint64(event.Timestamp.UnixNano()))
	return msg
}

// collectBLS collects BLS signatures from validators and aggregates them
func (q *Quasar) collectBLS(event FinalityEvent, msg []byte) ([]byte, []byte, uint64, error) {
	var signerBitset []byte
	var signerWeight uint64
	signatures := make([]*quasar.QuasarSig, 0, len(event.Validators))

	for i, v := range event.Validators {
		if !v.Active {
			continue
		}

		sig, err := q.core.SignMessage(v.NodeID.String(), msg)
		if err != nil {
			continue // Skip failed signers
		}

		signatures = append(signatures, sig)
		signerWeight += v.Weight

		// Set bit
		byteIdx := i / 8
		for len(signerBitset) <= byteIdx {
			signerBitset = append(signerBitset, 0)
		}
		signerBitset[byteIdx] |= 1 << uint(i%8)
	}

	if len(signatures) == 0 {
		return nil, nil, 0, errors.New("no BLS signatures")
	}

	agg, err := q.core.AggregateSignatures(msg, signatures)
	if err != nil {
		return nil, nil, 0, err
	}

	return agg.BLSAggregated, signerBitset, signerWeight, nil
}

// collectCorona runs the 2-round Corona threshold protocol in parallel
func (q *Quasar) collectCorona(message string) (Signature, error) {
	if q.corona == nil {
		return nil, ErrCoronaNotConnected
	}

	// Use the high-level Sign API which handles all rounds internally
	sig, err := q.corona.Sign([]byte(message))
	if err != nil {
		return nil, fmt.Errorf("corona signing failed: %w", err)
	}

	// Verify the signature
	if !q.corona.Verify([]byte(message), sig) {
		return nil, ErrCoronaFailed
	}

	q.log.Debug("corona signature complete",
		"signers", len(sig.Signers()),
		"type", sig.Type(),
	)

	return sig, nil
}

// totalWeight calculates total validator weight
func (q *Quasar) totalWeight(validators []ValidatorState) uint64 {
	var total uint64
	for _, v := range validators {
		if v.Active {
			total += v.Weight
		}
	}
	return total
}

// checkQuorum verifies quorum is met using cross-multiplication to avoid
// integer division truncation.
//
//	signerWeight / totalWeight >= quorumNum / quorumDen
//	is equivalent to:
//	signerWeight * quorumDen >= totalWeight * quorumNum
//
// Overflow bound: safe for totalWeight < 2^62 with quorumNum <= 3.
// Production values: totalWeight is sum of validator weights (well under 2^60),
// quorumNum=2, quorumDen=3.
func (q *Quasar) checkQuorum(signerWeight, totalWeight uint64) bool {
	return signerWeight*q.quorumDen >= totalWeight*q.quorumNum
}

// GetFinality returns finality for a block
func (q *Quasar) GetFinality(blockID ids.ID) (*QuantumFinality, bool) {
	q.mu.RLock()
	defer q.mu.RUnlock()
	f, ok := q.finalized[blockID]
	return f, ok
}

// Subscribe returns channel for finality events
func (q *Quasar) Subscribe() <-chan *QuantumFinality {
	return q.finalityCh
}

// Verify verifies a hybrid finality proof.
// Both BLS and Corona proofs are REQUIRED - no fallback mode.
// This ensures quantum-safe consensus for Q-Chain validators.
func (q *Quasar) Verify(finality *QuantumFinality) error {
	if finality == nil {
		return ErrFinalityFailed
	}

	// STRICT: Both proofs are REQUIRED
	if len(finality.BLSProof) == 0 {
		return fmt.Errorf("%w: BLS proof missing", ErrBLSFailed)
	}
	if len(finality.CoronaProof) == 0 {
		return fmt.Errorf("%w: RT proof missing - required for Q-Chain validators", ErrCoronaFailed)
	}

	if !q.checkQuorum(finality.SignerWeight, finality.TotalWeight) {
		return ErrInsufficientWeight
	}

	// Verify BLS via hybrid engine
	agg := &quasar.AggregatedSignature{
		BLSAggregated: finality.BLSProof,
	}

	// Reconstruct message for verification
	msg := make([]byte, 48)
	copy(msg[:32], finality.BlockID[:])
	putUint64BE(msg[32:40], finality.PChainHeight)
	putUint64BE(msg[40:48], uint64(finality.Timestamp.UnixNano()))

	if !q.core.VerifyAggregatedSignature(msg, agg) {
		return ErrBLSFailed
	}

	// Verify Corona threshold signature
	// RT signatures MUST have the "RT" prefix marker followed by threshold data
	if len(finality.CoronaProof) < 3 {
		return fmt.Errorf("%w: RT proof too short", ErrCoronaFailed)
	}
	if finality.CoronaProof[0] != 'R' || finality.CoronaProof[1] != 'T' {
		return fmt.Errorf("%w: invalid RT proof marker", ErrCoronaFailed)
	}

	// Verify threshold signers meet minimum requirement
	if len(finality.CoronaSigners) < q.threshold {
		return fmt.Errorf("%w: need %d signers, have %d",
			ErrInsufficientSigners, q.threshold, len(finality.CoronaSigners))
	}

	return nil
}

// Stats returns quasar statistics
func (q *Quasar) Stats() QuasarStats {
	q.mu.RLock()
	defer q.mu.RUnlock()

	var coronaStats CoronaStats
	if q.corona != nil {
		coronaStats = q.corona.Stats()
	}

	return QuasarStats{
		PChainHeight:    q.pHeight,
		QChainHeight:    q.qHeight,
		FinalizedBlocks: len(q.finalized),
		Threshold:       q.threshold,
		QuorumNum:       q.quorumNum,
		QuorumDen:       q.quorumDen,
		Running:         q.running,
		CoronaParties:   coronaStats.NumParties,
		CoronaThreshold: coronaStats.Threshold,
		CoronaReady:     coronaStats.Initialized,
	}
}

// QuasarStats contains quasar statistics
type QuasarStats struct {
	PChainHeight    uint64
	QChainHeight    uint64
	FinalizedBlocks int
	Threshold       int
	QuorumNum       uint64
	QuorumDen       uint64
	Running         bool
	CoronaParties   int
	CoronaThreshold int
	CoronaReady     bool
}

// GetCore returns the underlying quasar core for testing
func (q *Quasar) GetCore() *quasar.Quasar {
	return q.core
}

// GetCorona returns the Corona coordinator
func (q *Quasar) GetCorona() *CoronaCoordinator {
	q.mu.RLock()
	defer q.mu.RUnlock()
	return q.corona
}

// CheckQuorum verifies quorum is met (exported for testing)
func (q *Quasar) CheckQuorum(signerWeight, totalWeight uint64) bool {
	return q.checkQuorum(signerWeight, totalWeight)
}

// CreateMessage creates the finality message to sign (exported for testing)
func (q *Quasar) CreateMessage(event FinalityEvent) []byte {
	return q.createMessage(event)
}

// TotalWeight calculates total validator weight (exported for testing)
func (q *Quasar) TotalWeight(validators []ValidatorState) uint64 {
	return q.totalWeight(validators)
}

// GetConfig returns quorum configuration (exported for testing)
func (q *Quasar) GetConfig() (threshold int, quorumNum, quorumDen uint64) {
	q.mu.RLock()
	defer q.mu.RUnlock()
	return q.threshold, q.quorumNum, q.quorumDen
}

// IsRunning returns whether the Quasar is currently running (exported for testing)
func (q *Quasar) IsRunning() bool {
	q.mu.RLock()
	defer q.mu.RUnlock()
	return q.running
}

// SetFinalized adds a finality record (exported for testing/benchmarking)
func (q *Quasar) SetFinalized(blockID ids.ID, finality *QuantumFinality) {
	q.mu.Lock()
	defer q.mu.Unlock()
	q.finalized[blockID] = finality
}

// GetFinalized retrieves a finality record (exported for testing)
func (q *Quasar) GetFinalized(blockID ids.ID) (*QuantumFinality, bool) {
	q.mu.RLock()
	defer q.mu.RUnlock()
	f, ok := q.finalized[blockID]
	return f, ok
}

// Helper: big-endian uint64
func putUint64BE(b []byte, v uint64) {
	for i := 0; i < 8; i++ {
		b[i] = byte(v >> (56 - i*8))
	}
}
