package backbeat

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/hashicorp/raft"
	raftboltdb "github.com/hashicorp/raft-boltdb/v2"
	"github.com/rs/zerolog"
)

// LeaderElector implements BACKBEAT-REQ-001 (Pulse Leader)
// Provides pluggable leader election using Raft consensus
type LeaderElector struct {
	mu             sync.RWMutex
	raft           *raft.Raft
	nodeID         string
	bindAddr       string
	dataDir        string
	isLeader       bool
	leaderCh       chan bool
	shutdownCh     chan struct{}
	logger         zerolog.Logger
	onBecomeLeader func()
	onLoseLeader   func()
}

// FSM implements the Raft finite state machine for BACKBEAT state
type BackbeatFSM struct {
	mu    sync.RWMutex
	state map[string]interface{}
}

// LeaderElectorConfig configures the leader election
type LeaderElectorConfig struct {
	NodeID         string
	BindAddr       string
	DataDir        string
	Logger         zerolog.Logger
	OnBecomeLeader func()
	OnLoseLeader   func()
	Bootstrap      bool
	Peers          []string
}

// NewLeaderElector creates a new leader elector for BACKBEAT-REQ-001
func NewLeaderElector(config LeaderElectorConfig) (*LeaderElector, error) {
	if config.NodeID == "" {
		return nil, fmt.Errorf("node ID is required")
	}

	if config.BindAddr == "" {
		config.BindAddr = "127.0.0.1:0" // Let system assign port
	}

	if config.DataDir == "" {
		config.DataDir = filepath.Join(os.TempDir(), "backbeat-raft-"+config.NodeID)
	}

	// Create data directory
	if err := os.MkdirAll(config.DataDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create data directory: %v", err)
	}

	le := &LeaderElector{
		nodeID:         config.NodeID,
		bindAddr:       config.BindAddr,
		dataDir:        config.DataDir,
		logger:         config.Logger.With().Str("component", "leader-elector").Logger(),
		leaderCh:       make(chan bool, 1),
		shutdownCh:     make(chan struct{}),
		onBecomeLeader: config.OnBecomeLeader,
		onLoseLeader:   config.OnLoseLeader,
	}

	if err := le.setupRaft(config.Bootstrap, config.Peers); err != nil {
		return nil, fmt.Errorf("failed to setup Raft: %v", err)
	}

	go le.monitorLeadership()

	return le, nil
}

// setupRaft initializes the Raft consensus system
func (le *LeaderElector) setupRaft(bootstrap bool, peers []string) error {
	// Create Raft configuration
	config := raft.DefaultConfig()
	config.LocalID = raft.ServerID(le.nodeID)
	config.HeartbeatTimeout = 1 * time.Second
	config.ElectionTimeout = 1 * time.Second
	config.CommitTimeout = 500 * time.Millisecond
	config.LeaderLeaseTimeout = 500 * time.Millisecond

	// Setup logging will be handled by Raft's default logger

	// Create transport
	addr, err := net.ResolveTCPAddr("tcp", le.bindAddr)
	if err != nil {
		return fmt.Errorf("failed to resolve bind address: %v", err)
	}

	transport, err := raft.NewTCPTransport(le.bindAddr, addr, 3, 10*time.Second, os.Stderr)
	if err != nil {
		return fmt.Errorf("failed to create transport: %v", err)
	}

	// Update bind address with actual port if it was auto-assigned
	le.bindAddr = string(transport.LocalAddr())

	// Create the snapshot store
	snapshots, err := raft.NewFileSnapshotStore(le.dataDir, 2, os.Stderr)
	if err != nil {
		return fmt.Errorf("failed to create snapshot store: %v", err)
	}

	// Create the log store and stable store
	logStore, err := raftboltdb.NewBoltStore(filepath.Join(le.dataDir, "raft-log.bolt"))
	if err != nil {
		return fmt.Errorf("failed to create log store: %v", err)
	}

	stableStore, err := raftboltdb.NewBoltStore(filepath.Join(le.dataDir, "raft-stable.bolt"))
	if err != nil {
		return fmt.Errorf("failed to create stable store: %v", err)
	}

	// Create FSM
	fsm := &BackbeatFSM{
		state: make(map[string]interface{}),
	}

	// Create Raft instance
	r, err := raft.NewRaft(config, fsm, logStore, stableStore, snapshots, transport)
	if err != nil {
		return fmt.Errorf("failed to create Raft instance: %v", err)
	}

	le.raft = r

	// Bootstrap cluster if needed
	if bootstrap {
		servers := []raft.Server{
			{
				ID:      config.LocalID,
				Address: transport.LocalAddr(),
			},
		}

		// Add peer servers
		for _, peer := range peers {
			servers = append(servers, raft.Server{
				ID:      raft.ServerID(peer),
				Address: raft.ServerAddress(peer),
			})
		}

		configuration := raft.Configuration{Servers: servers}
		r.BootstrapCluster(configuration)
	}

	return nil
}

// monitorLeadership watches for leadership changes
func (le *LeaderElector) monitorLeadership() {
	for {
		select {
		case isLeader := <-le.raft.LeaderCh():
			le.mu.Lock()
			wasLeader := le.isLeader
			le.isLeader = isLeader
			le.mu.Unlock()

			if isLeader && !wasLeader {
				le.logger.Info().Msg("became leader")
				if le.onBecomeLeader != nil {
					le.onBecomeLeader()
				}
			} else if !isLeader && wasLeader {
				le.logger.Info().Msg("lost leadership")
				if le.onLoseLeader != nil {
					le.onLoseLeader()
				}
			}

			// Notify any waiting goroutines
			select {
			case le.leaderCh <- isLeader:
			default:
			}

		case <-le.shutdownCh:
			return
		}
	}
}

// IsLeader returns true if this node is the current leader
func (le *LeaderElector) IsLeader() bool {
	le.mu.RLock()
	defer le.mu.RUnlock()
	return le.isLeader
}

// GetLeader returns the current leader address
func (le *LeaderElector) GetLeader() string {
	if le.raft == nil {
		return ""
	}
	_, leaderAddr := le.raft.LeaderWithID()
	return string(leaderAddr)
}

// WaitForLeader blocks until leadership is established (this node or another)
func (le *LeaderElector) WaitForLeader(ctx context.Context) error {
	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			if leader := le.GetLeader(); leader != "" {
				return nil
			}
		}
	}
}

// Shutdown gracefully shuts down the leader elector
func (le *LeaderElector) Shutdown() error {
	close(le.shutdownCh)

	if le.raft != nil {
		shutdownFuture := le.raft.Shutdown()
		if err := shutdownFuture.Error(); err != nil {
			le.logger.Error().Err(err).Msg("failed to shutdown Raft")
			return err
		}
	}

	return nil
}

// GetStats returns Raft statistics for monitoring
func (le *LeaderElector) GetStats() map[string]interface{} {
	if le.raft == nil {
		return nil
	}

	stats := le.raft.Stats()
	result := make(map[string]interface{})
	for k, v := range stats {
		result[k] = v
	}

	result["is_leader"] = le.IsLeader()
	result["leader"] = le.GetLeader()
	result["node_id"] = le.nodeID
	result["bind_addr"] = le.bindAddr

	return result
}

// BackbeatFSM implementation
func (fsm *BackbeatFSM) Apply(log *raft.Log) interface{} {
	fsm.mu.Lock()
	defer fsm.mu.Unlock()

	// Parse the command
	var cmd map[string]interface{}
	if err := json.Unmarshal(log.Data, &cmd); err != nil {
		return err
	}

	// Apply command to state
	for k, v := range cmd {
		fsm.state[k] = v
	}

	return nil
}

func (fsm *BackbeatFSM) Snapshot() (raft.FSMSnapshot, error) {
	fsm.mu.RLock()
	defer fsm.mu.RUnlock()

	// Create a copy of the state
	state := make(map[string]interface{})
	for k, v := range fsm.state {
		state[k] = v
	}

	return &BackbeatSnapshot{state: state}, nil
}

func (fsm *BackbeatFSM) Restore(rc io.ReadCloser) error {
	defer rc.Close()

	var state map[string]interface{}
	decoder := json.NewDecoder(rc)
	if err := decoder.Decode(&state); err != nil {
		return err
	}

	fsm.mu.Lock()
	defer fsm.mu.Unlock()
	fsm.state = state

	return nil
}

// BackbeatSnapshot implements raft.FSMSnapshot
type BackbeatSnapshot struct {
	state map[string]interface{}
}

func (s *BackbeatSnapshot) Persist(sink raft.SnapshotSink) error {
	encoder := json.NewEncoder(sink)
	if err := encoder.Encode(s.state); err != nil {
		sink.Cancel()
		return err
	}
	return sink.Close()
}

func (s *BackbeatSnapshot) Release() {}
