package consensus

import (
	"context"
	"dejo_node/internal/mempool"
	"dejo_node/internal/staking"
	"dejo_node/internal/state"
	"dejo_node/internal/storage"
	"dejo_node/internal/transactions"
	"log"
	"time"
)

const (
	PhaseProposal  = "PROPOSAL"
	PhasePrevote   = "PREVOTE"
	PhasePrecommit = "PRECOMMIT"

	rewardAmount     = 5
	MaxRoundTimeout  = 10 * time.Second
)

func StartConsensusLoop(
	ctx context.Context,
	nodeID string,
	roundState *RoundState,
	broadcast func(msg ConsensusMessage),
	totalValidators int,
	store *storage.BlockStore,
	createBlockFn func() *transactions.Block,
	stakingStore *staking.StakingStore,
	minStake uint64,
	liveness *LivenessMonitor,
) {
	log.Println("🚀 Iniciando loop de consenso para altura", roundState.Height)
	if store == nil {
		log.Fatal("❌ ERRO: BlockStore está nil no StartConsensusLoop")
	}

	phase := PhaseProposal
	ticker := time.NewTicker(2 * time.Second)
	defer ticker.Stop()

	var proposedBlock *transactions.Block
	validatorSet := NewValidatorSetFromStaking(stakingStore, minStake)
	globalState := state.NewState()
	_ = globalState.LoadFromDisk("data/state.gob")
	pool := mempool.NewMempool()

	if !validatorSet.IsValidator(nodeID) {
		log.Println("⚠️ Este nó não é validador ativo — encerrando consenso nesta altura")
		return
	}

	proposer := validatorSet.SelectProposer(roundState.Height)
	if proposer == nil {
		log.Println("❌ Nenhum propositor válido encontrado")
		return
	}

	roundState.LastRoundStart = time.Now()

	for {
		select {
		case <-ctx.Done():
			log.Println("🛑 Loop de consenso encerrado")
			return
		case <-ticker.C:
			roundState.Mu.Lock()

			if time.Since(roundState.LastRoundStart) > MaxRoundTimeout {
				log.Println("⏰ Timeout! Reiniciando round", roundState.Round+1)
				roundState.ResetRound(roundState.Round + 1)
				roundState.LastRoundStart = time.Now()
				phase = PhaseProposal
				roundState.Mu.Unlock()
				continue
			}

			switch phase {
			case PhaseProposal:
				if proposer.Address != nodeID {
					log.Println("⏳ Aguardando proposta do propositor", proposer.Address)
					phase = PhasePrevote
					break
				}
				log.Println("📤 Fase de PROPOSTA - propondo bloco")
				latest, err := store.GetLatestBlock()
				if err != nil {
					log.Println("⚠️ Nenhum bloco encontrado, utilizando bloco base")
					latest = &transactions.Block{Index: 0, Hash: "genesis"}
				}
				proposedBlock = ProposeBlock(uint64(latest.Index+1), latest.Hash, pool)
				proposal := ProposalMsg{
					BaseMsg: BaseMsg{
						MsgType:   ProposalType,
						HeightVal: roundState.Height,
						RoundVal:  roundState.Round,
						Validator: nodeID,
						Time:      time.Now(),
					},
					BlockHash: proposedBlock.Hash,
				}
				roundState.Proposal = proposedBlock.Hash
				broadcast(proposal)
				phase = PhasePrevote

			case PhasePrevote:
				log.Println("🗳️ Fase de PREVOTE")
				vote := PrevoteMsg{
					BaseMsg: BaseMsg{
						MsgType:   PrevoteType,
						HeightVal: roundState.Height,
						RoundVal:  roundState.Round,
						Validator: nodeID,
						Time:      time.Now(),
					},
					BlockHash: roundState.Proposal,
				}
				roundState.Prevotes[nodeID] = vote.BlockHash
				broadcast(vote)
				phase = PhasePrecommit

			case PhasePrecommit:
				log.Println("🔐 Fase de PRECOMMIT")
				vote := PrecommitMsg{
					BaseMsg: BaseMsg{
						MsgType:   PrecommitType,
						HeightVal: roundState.Height,
						RoundVal:  roundState.Round,
						Validator: nodeID,
						Time:      time.Now(),
					},
					BlockHash: roundState.Proposal,
				}
				roundState.Precommits[nodeID] = vote.BlockHash
				broadcast(vote)

				quorumReached, blockHash := CheckQuorumWeighted(roundState.Precommits, validatorSet, liveness)
				if quorumReached {
					log.Println("🎉 Quórum alcançado! Bloco finalizado:", blockHash)
					if nodeID == proposer.Address && proposedBlock != nil && proposedBlock.Hash == blockHash {
						totalVotedStake := uint64(0)
						votedStakes := make(map[string]uint64)
						for validatorID, votedHash := range roundState.Precommits {
							if votedHash == blockHash && validatorSet.IsValidator(validatorID) {
								val, _ := validatorSet.ValidatorByAddress(validatorID)
								votedStakes[validatorID] = val.Stake
								totalVotedStake += val.Stake
							}
						}
						for validatorID, stake := range votedStakes {
							reward := (stake * rewardAmount) / totalVotedStake
							globalState.Mint(validatorID, reward)
							log.Printf("💸 Recompensa dinâmica de %d tokens para %s\n", reward, validatorID)
						}
						_ = globalState.SaveToDisk("data/state.gob")
						if err := store.SaveBlock(proposedBlock); err != nil {
							log.Println("❌ Erro ao persistir bloco:", err)
						} else {
							log.Println("💾 Bloco persistido com sucesso! Height:", proposedBlock.Index)
							pool.Clear()
						}
					}
					ApplySlash(roundState.Precommits, blockHash, stakingStore, validatorSet)
				}
				roundState.ResetRound(roundState.Round + 1)
				roundState.LastRoundStart = time.Now()
				phase = PhaseProposal
			}
			roundState.Mu.Unlock()
		}
	}
}