package locking

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"sync"
	"time"
)

// LockBackend defines the interface for lock backends
type LockBackend interface {
	// Acquire attempts to acquire a lock
	Acquire(key string, ttl time.Duration) (string, error)
	// Release releases a lock
	Release(key string, token string) error
	// Renew renews a lock's TTL
	Renew(key string, token string, ttl time.Duration) error
	// IsLocked checks if a lock is held
	IsLocked(key string) (bool, error)
}

// LocalLockBackend is a fallback in-memory lock backend
type LocalLockBackend struct {
	mu    sync.RWMutex
	locks map[string]string
}

// NewLocalLockBackend creates a new local lock backend
func NewLocalLockBackend() *LocalLockBackend {
	return &LocalLockBackend{
		locks: make(map[string]string),
	}
}

// Acquire acquires a lock locally
func (lb *LocalLockBackend) Acquire(key string, ttl time.Duration) (string, error) {
	lb.mu.Lock()
	defer lb.mu.Unlock()

	if _, exists := lb.locks[key]; exists {
		return "", fmt.Errorf("lock already held")
	}

	token := generateToken()
	lb.locks[key] = token

	return token, nil
}

// Release releases a lock locally
func (lb *LocalLockBackend) Release(key string, token string) error {
	lb.mu.Lock()
	defer lb.mu.Unlock()

	if held, exists := lb.locks[key]; !exists || held != token {
		return fmt.Errorf("lock not held by token")
	}

	delete(lb.locks, key)
	return nil
}

// Renew renews a lock locally (no-op for local backend)
func (lb *LocalLockBackend) Renew(key string, token string, ttl time.Duration) error {
	lb.mu.RLock()
	defer lb.mu.RUnlock()

	if held, exists := lb.locks[key]; !exists || held != token {
		return fmt.Errorf("lock not held by token")
	}

	return nil
}

// IsLocked checks if a lock is held locally
func (lb *LocalLockBackend) IsLocked(key string) (bool, error) {
	lb.mu.RLock()
	defer lb.mu.RUnlock()

	_, exists := lb.locks[key]
	return exists, nil
}

// DistributedLock represents a distributed lock
type DistributedLock struct {
	key        string
	token      string
	backend    LockBackend
	mu         sync.RWMutex
	acquired   bool
	acquiredAt time.Time
	ttl        time.Duration
}

// NewDistributedLock creates a new distributed lock
func NewDistributedLock(key string, backend LockBackend, ttl time.Duration) *DistributedLock {
	if ttl == 0 {
		ttl = 30 * time.Second // Default TTL
	}

	return &DistributedLock{
		key:     key,
		backend: backend,
		ttl:     ttl,
	}
}

// Acquire acquires the lock with timeout
func (dl *DistributedLock) Acquire(timeout time.Duration) error {
	if timeout == 0 {
		timeout = 5 * time.Second // Default timeout
	}

	deadline := time.Now().Add(timeout)

	for {
		token, err := dl.backend.Acquire(dl.key, dl.ttl)
		if err == nil {
			dl.mu.Lock()
			dl.token = token
			dl.acquired = true
			dl.acquiredAt = time.Now()
			dl.mu.Unlock()

			return nil
		}

		if time.Now().After(deadline) {
			return fmt.Errorf("lock acquisition timeout")
		}

		// Back off before retrying
		time.Sleep(100 * time.Millisecond)
	}
}

// Release releases the lock
func (dl *DistributedLock) Release() error {
	dl.mu.Lock()
	defer dl.mu.Unlock()

	if !dl.acquired {
		return fmt.Errorf("lock not acquired")
	}

	err := dl.backend.Release(dl.key, dl.token)
	if err == nil {
		dl.acquired = false
		dl.token = ""
	}

	return err
}

// Renew renews the lock's TTL
func (dl *DistributedLock) Renew() error {
	dl.mu.RLock()
	defer dl.mu.RUnlock()

	if !dl.acquired {
		return fmt.Errorf("lock not acquired")
	}

	return dl.backend.Renew(dl.key, dl.token, dl.ttl)
}

// IsAcquired checks if the lock is currently acquired
func (dl *DistributedLock) IsAcquired() bool {
	dl.mu.RLock()
	defer dl.mu.RUnlock()

	return dl.acquired
}

// GetAcquiredAt returns when the lock was acquired
func (dl *DistributedLock) GetAcquiredAt() time.Time {
	dl.mu.RLock()
	defer dl.mu.RUnlock()

	return dl.acquiredAt
}

// GetHoldDuration returns how long the lock has been held
func (dl *DistributedLock) GetHoldDuration() time.Duration {
	dl.mu.RLock()
	defer dl.mu.RUnlock()

	if !dl.acquired {
		return 0
	}

	return time.Since(dl.acquiredAt)
}

// LockManager manages multiple distributed locks
type LockManager struct {
	mu       sync.RWMutex
	backend  LockBackend
	locks    map[string]*DistributedLock
	lockTTL  time.Duration
	stats    *LockStats
}

// LockStats tracks lock statistics
type LockStats struct {
	TotalAcquisitions int
	TotalReleases     int
	FailedAcquisitions int
	ActiveLocks       int
	AverageLockTime   time.Duration
}

// NewLockManager creates a new lock manager
func NewLockManager(backend LockBackend, lockTTL time.Duration) *LockManager {
	if lockTTL == 0 {
		lockTTL = 30 * time.Second
	}

	return &LockManager{
		backend: backend,
		locks:   make(map[string]*DistributedLock),
		lockTTL: lockTTL,
		stats: &LockStats{
			TotalAcquisitions: 0,
			TotalReleases:     0,
		},
	}
}

// AcquireLock acquires or retrieves an existing lock
func (lm *LockManager) AcquireLock(key string, timeout time.Duration) error {
	lm.mu.Lock()
	defer lm.mu.Unlock()

	// Check if lock already exists and is acquired
	if lock, exists := lm.locks[key]; exists && lock.IsAcquired() {
		return fmt.Errorf("lock already acquired by this manager")
	}

	lock := NewDistributedLock(key, lm.backend, lm.lockTTL)
	err := lock.Acquire(timeout)
	if err != nil {
		lm.stats.FailedAcquisitions++
		return err
	}

	lm.locks[key] = lock
	lm.stats.TotalAcquisitions++
	lm.stats.ActiveLocks++

	return nil
}

// ReleaseLock releases a lock
func (lm *LockManager) ReleaseLock(key string) error {
	lm.mu.Lock()
	defer lm.mu.Unlock()

	lock, exists := lm.locks[key]
	if !exists {
		return fmt.Errorf("lock not found")
	}

	err := lock.Release()
	if err == nil {
		lm.stats.TotalReleases++
		lm.stats.ActiveLocks--
		delete(lm.locks, key)
	}

	return err
}

// RenewLock renews a lock
func (lm *LockManager) RenewLock(key string) error {
	lm.mu.RLock()
	defer lm.mu.RUnlock()

	lock, exists := lm.locks[key]
	if !exists {
		return fmt.Errorf("lock not found")
	}

	return lock.Renew()
}

// GetLockStats returns lock statistics
func (lm *LockManager) GetLockStats() *LockStats {
	lm.mu.RLock()
	defer lm.mu.RUnlock()

	stats := *lm.stats
	return &stats
}

// GetActiveLocks returns list of active lock keys
func (lm *LockManager) GetActiveLocks() []string {
	lm.mu.RLock()
	defer lm.mu.RUnlock()

	keys := make([]string, 0, len(lm.locks))
	for key, lock := range lm.locks {
		if lock.IsAcquired() {
			keys = append(keys, key)
		}
	}

	return keys
}

// ReleaseAll releases all locks
func (lm *LockManager) ReleaseAll() error {
	lm.mu.Lock()
	defer lm.mu.Unlock()

	var lastErr error
	for key, lock := range lm.locks {
		if lock.IsAcquired() {
			err := lock.Release()
			if err != nil {
				lastErr = err
			}
			delete(lm.locks, key)
		}
	}

	lm.stats.ActiveLocks = 0
	return lastErr
}

// generateToken generates a random token for lock identification
func generateToken() string {
	b := make([]byte, 16)
	rand.Read(b)
	return hex.EncodeToString(b)
}
