package audit

import (
	"net/http"
	"sync"

	"github.com/pingcap/log"
	"go.uber.org/zap"
)

// Manager manages the audit logger lifecycle and handles hot-reload of audit configuration.
// It implements AuditConfigChangeHandler interface for receiving config updates.
type Manager struct {
	mu     sync.RWMutex
	logger *Logger
}

// NewManager creates a new audit manager.
// If cfg.Enabled is true, it creates the underlying Logger.
func NewManager(cfg *Config) (*Manager, error) {
	m := &Manager{}

	if cfg.Enabled {
		logger, err := NewLogger(cfg)
		if err != nil {
			return nil, err
		}
		m.logger = logger
	}

	return m, nil
}

// OnAuditConfigChange handles audit configuration changes.
// It creates, updates, or destroys the underlying logger based on the Enabled field.
func (m *Manager) OnAuditConfigChange(cfg *Config) error {
	// Defensive validation to prevent invalid config from being applied
	if err := ValidateConfig(cfg); err != nil {
		log.Error("invalid audit config in hot reload", zap.Error(err))
		return err
	}

	// Case 1/2: (Enabled -> disabled) or (disabled -> still disabled)
	// Note: Close errors are logged but not returned because:
	// 1. The config change (disabling) should still take effect
	// 2. Close failures are typically non-fatal (e.g., buffer flush issues)
	// 3. Returning error would prevent the state transition
	if !cfg.Enabled {
		var toClose *Logger

		m.mu.Lock()
		toClose = m.logger
		m.logger = nil
		m.mu.Unlock()

		if toClose != nil {
			log.Info("audit disabled, closing logger")
			if err := toClose.Close(); err != nil {
				log.Error("failed to close audit logger", zap.Error(err))
			}
		}
		return nil
	}

	// Case 3/4: Enabled -> enabled (hot update) or disabled -> enabled (create new logger)
	m.mu.RLock()
	current := m.logger
	m.mu.RUnlock()

	if current != nil {
		log.Info("updating audit hot config")
		current.updateHotConfig(cfg)
		return nil
	}

	log.Info("audit enabled, creating logger", zap.String("filePath", cfg.FilePath))
	newLogger, err := NewLogger(cfg)
	if err != nil {
		log.Error("failed to create audit logger", zap.Error(err))
		return err
	}

	// Swap-in with minimal lock hold; close the unused logger if a concurrent creator won.
	m.mu.Lock()
	if m.logger != nil {
		m.mu.Unlock()
		_ = newLogger.Close()
		return nil
	}
	m.logger = newLogger
	m.mu.Unlock()
	return nil
}

// GetLogger returns the current logger.
// Returns nil if audit is disabled.
func (m *Manager) GetLogger() *Logger {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.logger
}

// IsEnabled returns true if audit logging is currently enabled.
func (m *Manager) IsEnabled() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.logger != nil
}

// GetMaxBodySize returns the current max body size for audit request body logging.
// Returns 0 if audit is disabled or request body logging is disabled.
func (m *Manager) GetMaxBodySize() int {
	m.mu.RLock()
	defer m.mu.RUnlock()
	if m.logger == nil {
		return 0
	}
	return m.logger.hot.getMaxBodySize()
}

// GetAuditSnapshot returns audit configuration as an atomic snapshot.
// This avoids race conditions when checking enabled and getting maxBodySize
// separately, ensuring consistent state between the two values.
// Returns enabled=false, maxBodySize=0 if audit is disabled.
func (m *Manager) GetAuditSnapshot() (enabled bool, maxBodySize int) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	if m.logger == nil {
		return false, 0
	}
	return true, m.logger.hot.getMaxBodySize()
}

// RecordRequest records an HTTP request to the audit log.
// It's safe to call even when audit is disabled (no-op).
// The lock is released early after reading the logger pointer.
// Logger.RecordRequest is safe to call concurrently with Close() because
// Logger.Log uses a done channel to detect shutdown.
func (m *Manager) RecordRequest(r *http.Request, opts RequestRecordOptions) {
	m.mu.RLock()
	logger := m.logger
	m.mu.RUnlock()

	if logger == nil {
		return
	}
	logger.RecordRequest(r, opts)
}

// Close closes the manager and the underlying logger.
func (m *Manager) Close() error {
	var toClose *Logger

	m.mu.Lock()
	toClose = m.logger
	m.logger = nil
	m.mu.Unlock()

	if toClose != nil {
		if err := toClose.Close(); err != nil {
			return err
		}
	}
	return nil
}
