// Package leaderelection provides distributed leader election for multiple application instances.
// It ensures only one instance acts as the leader at any given time, with automatic failover.
package leaderelection

import (
	"context"
	"time"
)

// LeaderElection provides distributed leader election across multiple application instances.
// It uses Redis to coordinate which instance is the current leader, with automatic failover
// if the leader crashes or becomes unavailable.
type LeaderElection interface {
	// Start begins participating in leader election.
	// This method blocks and runs the election loop until ctx is cancelled or an error occurs.
	// The instance will automatically attempt to become leader and maintain leadership.
	Start(ctx context.Context) error

	// IsLeader returns true if this instance is currently the leader.
	// This is a local check and does not require network communication.
	IsLeader() bool

	// GetLeaderID returns the unique identifier of the current leader instance.
	// Returns empty string if no leader exists (should be rare).
	GetLeaderID() (string, error)

	// GetLeaderInfo returns detailed information about the current leader.
	GetLeaderInfo() (*LeaderInfo, error)

	// OnBecomeLeader registers a callback function that will be executed when
	// this instance becomes the leader. Multiple callbacks can be registered.
	OnBecomeLeader(callback func())

	// OnLoseLeadership registers a callback function that will be executed when
	// this instance loses leadership (either voluntarily or due to failure).
	// Multiple callbacks can be registered.
	OnLoseLeadership(callback func())

	// Stop gracefully stops leader election participation.
	// If this instance is the leader, it releases leadership allowing another instance to take over.
	// This should be called during application shutdown.
	Stop() error

	// GetInstanceID returns the unique identifier for this instance.
	GetInstanceID() string
}

// LeaderInfo contains information about the current leader.
type LeaderInfo struct {
	// InstanceID is the unique identifier of the leader instance
	InstanceID string `json:"instance_id"`

	// Hostname is the hostname of the leader instance
	Hostname string `json:"hostname"`

	// StartedAt is when this instance became the leader
	StartedAt time.Time `json:"started_at"`

	// LastHeartbeat is the last time the leader renewed its lock
	LastHeartbeat time.Time `json:"last_heartbeat"`
}

// Config contains configuration for leader election.
type Config struct {
	// RedisKeyName is the Redis key used for leader election.
	// Default: "maplefile:leader:lock"
	RedisKeyName string

	// RedisInfoKeyName is the Redis key used to store leader information.
	// Default: "maplefile:leader:info"
	RedisInfoKeyName string

	// LockTTL is how long the leader lock lasts before expiring.
	// The leader must renew the lock before this time expires.
	// Default: 10 seconds
	// Recommended: 10-30 seconds
	LockTTL time.Duration

	// HeartbeatInterval is how often the leader renews its lock.
	// This should be significantly less than LockTTL (e.g., LockTTL / 3).
	// Default: 3 seconds
	// Recommended: LockTTL / 3
	HeartbeatInterval time.Duration

	// RetryInterval is how often followers check for leadership opportunity.
	// Default: 2 seconds
	// Recommended: 1-5 seconds
	RetryInterval time.Duration

	// InstanceID uniquely identifies this application instance.
	// If empty, will be auto-generated from hostname + random suffix.
	// Default: auto-generated
	InstanceID string

	// Hostname is the hostname of this instance.
	// If empty, will be auto-detected.
	// Default: os.Hostname()
	Hostname string
}

// DefaultConfig returns a Config with sensible defaults.
func DefaultConfig() *Config {
	return &Config{
		RedisKeyName:      "maplefile:leader:lock",
		RedisInfoKeyName:  "maplefile:leader:info",
		LockTTL:           10 * time.Second,
		HeartbeatInterval: 3 * time.Second,
		RetryInterval:     2 * time.Second,
	}
}

// Validate checks if the configuration is valid and returns an error if not.
func (c *Config) Validate() error {
	if c.RedisKeyName == "" {
		c.RedisKeyName = "maplefile:leader:lock"
	}
	if c.RedisInfoKeyName == "" {
		c.RedisInfoKeyName = "maplefile:leader:info"
	}
	if c.LockTTL <= 0 {
		c.LockTTL = 10 * time.Second
	}
	if c.HeartbeatInterval <= 0 {
		c.HeartbeatInterval = 3 * time.Second
	}
	if c.RetryInterval <= 0 {
		c.RetryInterval = 2 * time.Second
	}

	// HeartbeatInterval should be less than LockTTL
	if c.HeartbeatInterval >= c.LockTTL {
		c.HeartbeatInterval = c.LockTTL / 3
	}

	return nil
}
