package native

import (
	"encoding/binary"
	"errors"

	"git.marketally.com/tutus-one/tutus-chain/pkg/core/dao"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/storage"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
)

// ARCH-001: Circuit Breaker System
// Provides automatic protection against anomalous behavior by halting
// contract operations when thresholds are exceeded. This is a critical
// safety mechanism for production deployments.

// CircuitState represents the current state of a circuit breaker.
type CircuitState uint8

const (
	// CircuitClosed means normal operation (requests flow through)
	CircuitClosed CircuitState = iota
	// CircuitOpen means halted (requests are rejected)
	CircuitOpen
	// CircuitHalfOpen means testing recovery (limited requests allowed)
	CircuitHalfOpen
)

// TripReason identifies why a circuit breaker was tripped.
type TripReason uint8

const (
	TripReasonManual TripReason = iota
	TripReasonRateLimit
	TripReasonBalanceAnomaly
	TripReasonSecurityBreach
	TripReasonExternalDependency
	TripReasonResourceExhaustion
	TripReasonConsensusFailure
)

// CircuitBreakerConfig contains settings for a circuit breaker.
type CircuitBreakerConfig struct {
	// Name identifies this circuit breaker
	Name string
	// ContractID is the contract this breaker protects
	ContractID int32
	// FailureThreshold is failures before tripping
	FailureThreshold uint32
	// SuccessThreshold is successes needed to close after half-open
	SuccessThreshold uint32
	// TimeoutBlocks is blocks before moving from open to half-open
	TimeoutBlocks uint32
	// CooldownBlocks is minimum blocks between state changes
	CooldownBlocks uint32
	// AutoRecover determines if breaker can auto-recover
	AutoRecover bool
}

// CircuitBreakerState tracks the current state of a circuit breaker.
type CircuitBreakerState struct {
	State             CircuitState
	FailureCount      uint32
	SuccessCount      uint32
	LastStateChange   uint32
	LastFailure       uint32
	TripReason        TripReason
	TrippedBy         util.Uint160
	TotalTrips        uint64
	ConsecutiveTrips  uint32
}

// Storage prefixes for circuit breakers.
const (
	circuitPrefixConfig   byte = 0xCB // name -> CircuitBreakerConfig
	circuitPrefixState    byte = 0xCC // name -> CircuitBreakerState
	circuitPrefixHistory  byte = 0xCD // name + timestamp -> TripEvent
	circuitPrefixGlobal   byte = 0xCE // -> GlobalCircuitState
)

// Circuit breaker errors.
var (
	ErrCircuitOpen         = errors.New("circuit breaker is open")
	ErrCircuitHalfOpen     = errors.New("circuit breaker is half-open, limited operations")
	ErrCircuitCooldown     = errors.New("circuit breaker cooldown period active")
	ErrCircuitNotFound     = errors.New("circuit breaker not found")
	ErrCircuitAutoRecover  = errors.New("circuit breaker cannot be manually closed when auto-recover enabled")
)

// Default circuit breaker settings.
const (
	DefaultFailureThreshold = 10
	DefaultSuccessThreshold = 5
	DefaultTimeoutBlocks    = 100
	DefaultCooldownBlocks   = 10
)

// CircuitBreaker provides circuit breaker functionality for contracts.
type CircuitBreaker struct {
	contractID int32
}

// NewCircuitBreaker creates a new circuit breaker manager.
func NewCircuitBreaker(contractID int32) *CircuitBreaker {
	return &CircuitBreaker{contractID: contractID}
}

// RegisterBreaker registers a new circuit breaker with configuration.
func (cb *CircuitBreaker) RegisterBreaker(d *dao.Simple, cfg *CircuitBreakerConfig) {
	key := cb.makeConfigKey(cfg.Name)
	data := cb.serializeConfig(cfg)
	d.PutStorageItem(cb.contractID, key, data)

	// Initialize state as closed
	state := &CircuitBreakerState{
		State: CircuitClosed,
	}
	cb.putState(d, cfg.Name, state)
}

// GetState retrieves the current state of a circuit breaker.
func (cb *CircuitBreaker) GetState(d *dao.Simple, name string) *CircuitBreakerState {
	key := cb.makeStateKey(name)
	si := d.GetStorageItem(cb.contractID, key)
	if si == nil {
		return nil
	}
	return cb.deserializeState(si)
}

// GetConfig retrieves circuit breaker configuration.
func (cb *CircuitBreaker) GetConfig(d *dao.Simple, name string) *CircuitBreakerConfig {
	key := cb.makeConfigKey(name)
	si := d.GetStorageItem(cb.contractID, key)
	if si == nil {
		return nil
	}
	return cb.deserializeConfig(si)
}

// AllowRequest checks if a request should be allowed through.
func (cb *CircuitBreaker) AllowRequest(d *dao.Simple, name string, currentBlock uint32) error {
	state := cb.GetState(d, name)
	if state == nil {
		return ErrCircuitNotFound
	}

	switch state.State {
	case CircuitClosed:
		return nil
	case CircuitOpen:
		cfg := cb.GetConfig(d, name)
		if cfg == nil {
			return ErrCircuitNotFound
		}
		// Check if timeout has elapsed for potential recovery
		if cfg.AutoRecover && currentBlock >= state.LastStateChange+cfg.TimeoutBlocks {
			// Transition to half-open
			cb.transitionState(d, name, CircuitHalfOpen, currentBlock)
			return ErrCircuitHalfOpen
		}
		return ErrCircuitOpen
	case CircuitHalfOpen:
		return ErrCircuitHalfOpen
	}

	return nil
}

// RecordSuccess records a successful operation.
func (cb *CircuitBreaker) RecordSuccess(d *dao.Simple, name string, currentBlock uint32) {
	state := cb.GetState(d, name)
	if state == nil {
		return
	}

	if state.State == CircuitHalfOpen {
		state.SuccessCount++
		cfg := cb.GetConfig(d, name)
		if cfg != nil && state.SuccessCount >= cfg.SuccessThreshold {
			// Close the circuit
			cb.transitionState(d, name, CircuitClosed, currentBlock)
			return
		}
	}

	// Reset failure count on success when closed
	if state.State == CircuitClosed {
		state.FailureCount = 0
	}
	cb.putState(d, name, state)
}

// RecordFailure records a failed operation.
func (cb *CircuitBreaker) RecordFailure(d *dao.Simple, name string, currentBlock uint32, reason TripReason) {
	state := cb.GetState(d, name)
	if state == nil {
		return
	}

	state.FailureCount++
	state.LastFailure = currentBlock

	cfg := cb.GetConfig(d, name)
	if cfg == nil {
		return
	}

	switch state.State {
	case CircuitClosed:
		if state.FailureCount >= cfg.FailureThreshold {
			cb.tripBreaker(d, name, currentBlock, reason, util.Uint160{})
		} else {
			cb.putState(d, name, state)
		}
	case CircuitHalfOpen:
		// Any failure in half-open immediately trips
		cb.tripBreaker(d, name, currentBlock, reason, util.Uint160{})
	}
}

// TripBreaker manually trips a circuit breaker.
func (cb *CircuitBreaker) TripBreaker(d *dao.Simple, name string, currentBlock uint32, reason TripReason, tripper util.Uint160) error {
	state := cb.GetState(d, name)
	if state == nil {
		return ErrCircuitNotFound
	}

	cfg := cb.GetConfig(d, name)
	if cfg == nil {
		return ErrCircuitNotFound
	}

	// Check cooldown
	if currentBlock < state.LastStateChange+cfg.CooldownBlocks {
		return ErrCircuitCooldown
	}

	cb.tripBreaker(d, name, currentBlock, reason, tripper)
	return nil
}

// ResetBreaker manually resets a circuit breaker to closed.
func (cb *CircuitBreaker) ResetBreaker(d *dao.Simple, name string, currentBlock uint32) error {
	state := cb.GetState(d, name)
	if state == nil {
		return ErrCircuitNotFound
	}

	cfg := cb.GetConfig(d, name)
	if cfg == nil {
		return ErrCircuitNotFound
	}

	// Check cooldown
	if currentBlock < state.LastStateChange+cfg.CooldownBlocks {
		return ErrCircuitCooldown
	}

	cb.transitionState(d, name, CircuitClosed, currentBlock)
	return nil
}

// tripBreaker internal method to trip the breaker.
func (cb *CircuitBreaker) tripBreaker(d *dao.Simple, name string, currentBlock uint32, reason TripReason, tripper util.Uint160) {
	state := cb.GetState(d, name)
	if state == nil {
		return
	}

	state.State = CircuitOpen
	state.TripReason = reason
	state.TrippedBy = tripper
	state.LastStateChange = currentBlock
	state.TotalTrips++
	state.ConsecutiveTrips++
	state.SuccessCount = 0

	cb.putState(d, name, state)
	cb.recordTripEvent(d, name, currentBlock, reason, tripper)
}

// transitionState changes the circuit state.
func (cb *CircuitBreaker) transitionState(d *dao.Simple, name string, newState CircuitState, currentBlock uint32) {
	state := cb.GetState(d, name)
	if state == nil {
		return
	}

	state.State = newState
	state.LastStateChange = currentBlock

	if newState == CircuitClosed {
		state.FailureCount = 0
		state.SuccessCount = 0
		state.ConsecutiveTrips = 0
	} else if newState == CircuitHalfOpen {
		state.SuccessCount = 0
	}

	cb.putState(d, name, state)
}

// TripEvent records a circuit breaker trip for auditing.
type TripEvent struct {
	Name        string
	BlockHeight uint32
	Reason      TripReason
	TrippedBy   util.Uint160
}

// recordTripEvent stores a trip event in history.
func (cb *CircuitBreaker) recordTripEvent(d *dao.Simple, name string, blockHeight uint32, reason TripReason, tripper util.Uint160) {
	key := make([]byte, 1+len(name)+4)
	key[0] = circuitPrefixHistory
	copy(key[1:], name)
	binary.BigEndian.PutUint32(key[1+len(name):], blockHeight)

	data := make([]byte, 21)
	data[0] = byte(reason)
	copy(data[1:], tripper.BytesBE())
	d.PutStorageItem(cb.contractID, key, data)
}

// GetTripHistory retrieves trip history for a circuit breaker.
func (cb *CircuitBreaker) GetTripHistory(d *dao.Simple, name string, limit int) []TripEvent {
	var events []TripEvent
	prefix := make([]byte, 1+len(name))
	prefix[0] = circuitPrefixHistory
	copy(prefix[1:], name)

	count := 0
	d.Seek(cb.contractID, storage.SeekRange{Prefix: prefix, Backwards: true}, func(k, v []byte) bool {
		if count >= limit || len(k) < 4 || len(v) < 21 {
			return false
		}
		event := TripEvent{
			Name:        name,
			BlockHeight: binary.BigEndian.Uint32(k[len(k)-4:]),
			Reason:      TripReason(v[0]),
		}
		event.TrippedBy, _ = util.Uint160DecodeBytesBE(v[1:21])
		events = append(events, event)
		count++
		return true
	})

	return events
}

// IsOpen returns true if the circuit is open (blocking requests).
func (cb *CircuitBreaker) IsOpen(d *dao.Simple, name string) bool {
	state := cb.GetState(d, name)
	return state != nil && state.State == CircuitOpen
}

// IsClosed returns true if the circuit is closed (allowing requests).
func (cb *CircuitBreaker) IsClosed(d *dao.Simple, name string) bool {
	state := cb.GetState(d, name)
	return state != nil && state.State == CircuitClosed
}

// GlobalCircuitState tracks system-wide circuit breaker status.
type GlobalCircuitState struct {
	// EmergencyShutdown halts all protected operations
	EmergencyShutdown bool
	// ShutdownBlock is when emergency was triggered
	ShutdownBlock uint32
	// ShutdownBy is who triggered emergency
	ShutdownBy util.Uint160
	// ActiveBreakers is count of currently open breakers
	ActiveBreakers uint32
}

// GetGlobalState retrieves the global circuit breaker state.
func (cb *CircuitBreaker) GetGlobalState(d *dao.Simple) *GlobalCircuitState {
	key := []byte{circuitPrefixGlobal}
	si := d.GetStorageItem(cb.contractID, key)
	if si == nil {
		return &GlobalCircuitState{}
	}
	if len(si) < 26 {
		return &GlobalCircuitState{}
	}

	return &GlobalCircuitState{
		EmergencyShutdown: si[0] == 1,
		ShutdownBlock:     binary.BigEndian.Uint32(si[1:5]),
		ShutdownBy:        mustDecodeUint160(si[5:25]),
		ActiveBreakers:    binary.BigEndian.Uint32(si[25:29]),
	}
}

// SetEmergencyShutdown enables or disables emergency shutdown.
func (cb *CircuitBreaker) SetEmergencyShutdown(d *dao.Simple, enabled bool, blockHeight uint32, triggeredBy util.Uint160) {
	state := cb.GetGlobalState(d)
	state.EmergencyShutdown = enabled
	if enabled {
		state.ShutdownBlock = blockHeight
		state.ShutdownBy = triggeredBy
	}

	key := []byte{circuitPrefixGlobal}
	data := make([]byte, 29)
	if state.EmergencyShutdown {
		data[0] = 1
	}
	binary.BigEndian.PutUint32(data[1:5], state.ShutdownBlock)
	copy(data[5:25], state.ShutdownBy.BytesBE())
	binary.BigEndian.PutUint32(data[25:29], state.ActiveBreakers)
	d.PutStorageItem(cb.contractID, key, data)
}

// IsEmergencyShutdown returns true if emergency shutdown is active.
func (cb *CircuitBreaker) IsEmergencyShutdown(d *dao.Simple) bool {
	state := cb.GetGlobalState(d)
	return state.EmergencyShutdown
}

// Helper methods.
func (cb *CircuitBreaker) makeConfigKey(name string) []byte {
	key := make([]byte, 1+len(name))
	key[0] = circuitPrefixConfig
	copy(key[1:], name)
	return key
}

func (cb *CircuitBreaker) makeStateKey(name string) []byte {
	key := make([]byte, 1+len(name))
	key[0] = circuitPrefixState
	copy(key[1:], name)
	return key
}

func (cb *CircuitBreaker) putState(d *dao.Simple, name string, state *CircuitBreakerState) {
	key := cb.makeStateKey(name)
	data := cb.serializeState(state)
	d.PutStorageItem(cb.contractID, key, data)
}

// Serialization helpers.
func (cb *CircuitBreaker) serializeConfig(cfg *CircuitBreakerConfig) []byte {
	nameBytes := []byte(cfg.Name)
	data := make([]byte, 4+len(nameBytes)+4+16+1)
	offset := 0

	binary.BigEndian.PutUint32(data[offset:], uint32(len(nameBytes)))
	offset += 4
	copy(data[offset:], nameBytes)
	offset += len(nameBytes)

	binary.BigEndian.PutUint32(data[offset:], uint32(cfg.ContractID))
	offset += 4
	binary.BigEndian.PutUint32(data[offset:], cfg.FailureThreshold)
	offset += 4
	binary.BigEndian.PutUint32(data[offset:], cfg.SuccessThreshold)
	offset += 4
	binary.BigEndian.PutUint32(data[offset:], cfg.TimeoutBlocks)
	offset += 4
	binary.BigEndian.PutUint32(data[offset:], cfg.CooldownBlocks)
	offset += 4
	if cfg.AutoRecover {
		data[offset] = 1
	}

	return data
}

func (cb *CircuitBreaker) deserializeConfig(data []byte) *CircuitBreakerConfig {
	if len(data) < 8 {
		return nil
	}
	cfg := &CircuitBreakerConfig{}
	offset := 0

	nameLen := binary.BigEndian.Uint32(data[offset:])
	offset += 4
	if offset+int(nameLen) > len(data) {
		return nil
	}
	cfg.Name = string(data[offset : offset+int(nameLen)])
	offset += int(nameLen)

	if offset+17 > len(data) {
		return nil
	}

	cfg.ContractID = int32(binary.BigEndian.Uint32(data[offset:]))
	offset += 4
	cfg.FailureThreshold = binary.BigEndian.Uint32(data[offset:])
	offset += 4
	cfg.SuccessThreshold = binary.BigEndian.Uint32(data[offset:])
	offset += 4
	cfg.TimeoutBlocks = binary.BigEndian.Uint32(data[offset:])
	offset += 4
	cfg.CooldownBlocks = binary.BigEndian.Uint32(data[offset:])
	offset += 4
	cfg.AutoRecover = data[offset] == 1

	return cfg
}

func (cb *CircuitBreaker) serializeState(state *CircuitBreakerState) []byte {
	data := make([]byte, 46)
	data[0] = byte(state.State)
	binary.BigEndian.PutUint32(data[1:5], state.FailureCount)
	binary.BigEndian.PutUint32(data[5:9], state.SuccessCount)
	binary.BigEndian.PutUint32(data[9:13], state.LastStateChange)
	binary.BigEndian.PutUint32(data[13:17], state.LastFailure)
	data[17] = byte(state.TripReason)
	copy(data[18:38], state.TrippedBy.BytesBE())
	binary.BigEndian.PutUint64(data[38:46], state.TotalTrips)
	return data
}

func (cb *CircuitBreaker) deserializeState(data []byte) *CircuitBreakerState {
	if len(data) < 46 {
		return nil
	}
	state := &CircuitBreakerState{
		State:           CircuitState(data[0]),
		FailureCount:    binary.BigEndian.Uint32(data[1:5]),
		SuccessCount:    binary.BigEndian.Uint32(data[5:9]),
		LastStateChange: binary.BigEndian.Uint32(data[9:13]),
		LastFailure:     binary.BigEndian.Uint32(data[13:17]),
		TripReason:      TripReason(data[17]),
		TotalTrips:      binary.BigEndian.Uint64(data[38:46]),
	}
	state.TrippedBy, _ = util.Uint160DecodeBytesBE(data[18:38])
	return state
}

func mustDecodeUint160(data []byte) util.Uint160 {
	u, _ := util.Uint160DecodeBytesBE(data)
	return u
}

// StandardCircuitBreakers defines common circuit breaker names.
var StandardCircuitBreakers = struct {
	VTSTransfers       string
	VitaRegistration   string
	CrossChainBridge   string
	HealthRecords      string
	InvestmentOps      string
	GovernanceVoting   string
	TributeAssessment  string
}{
	VTSTransfers:       "vts_transfers",
	VitaRegistration:   "vita_registration",
	CrossChainBridge:   "cross_chain_bridge",
	HealthRecords:      "health_records",
	InvestmentOps:      "investment_ops",
	GovernanceVoting:   "governance_voting",
	TributeAssessment:  "tribute_assessment",
}
