// Package notify - Notification manager for fan-out to multiple backends
package notify

import (
	"context"
	"fmt"
	"os"
	"sync"
)

// Manager manages multiple notification backends
type Manager struct {
	config    Config
	notifiers []Notifier
	mu        sync.RWMutex
	hostname  string
}

// NewManager creates a new notification manager with configured backends
func NewManager(config Config) *Manager {
	hostname, _ := os.Hostname()

	m := &Manager{
		config:    config,
		notifiers: make([]Notifier, 0),
		hostname:  hostname,
	}

	// Initialize enabled backends
	if config.SMTPEnabled {
		m.notifiers = append(m.notifiers, NewSMTPNotifier(config))
	}

	if config.WebhookEnabled {
		m.notifiers = append(m.notifiers, NewWebhookNotifier(config))
	}

	return m
}

// AddNotifier adds a custom notifier to the manager
func (m *Manager) AddNotifier(n Notifier) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.notifiers = append(m.notifiers, n)
}

// Notify sends an event to all enabled notification backends
// This is a non-blocking operation that runs in a goroutine
func (m *Manager) Notify(event *Event) {
	go m.NotifySync(context.Background(), event)
}

// NotifySync sends an event synchronously to all enabled backends
func (m *Manager) NotifySync(ctx context.Context, event *Event) error {
	// Add hostname if not set
	if event.Hostname == "" && m.hostname != "" {
		event.Hostname = m.hostname
	}

	// Check if we should send based on event type/severity
	if !m.shouldSend(event) {
		return nil
	}

	m.mu.RLock()
	notifiers := make([]Notifier, len(m.notifiers))
	copy(notifiers, m.notifiers)
	m.mu.RUnlock()

	var errors []error
	var errMu sync.Mutex
	var wg sync.WaitGroup

	for _, n := range notifiers {
		if !n.IsEnabled() {
			continue
		}

		wg.Add(1)
		go func(notifier Notifier) {
			defer wg.Done()
			if err := notifier.Send(ctx, event); err != nil {
				errMu.Lock()
				errors = append(errors, fmt.Errorf("%s: %w", notifier.Name(), err))
				errMu.Unlock()
			}
		}(n)
	}

	wg.Wait()

	if len(errors) > 0 {
		return fmt.Errorf("notification errors: %v", errors)
	}
	return nil
}

// shouldSend determines if an event should be sent based on configuration
func (m *Manager) shouldSend(event *Event) bool {
	// Check minimum severity
	if !m.meetsSeverity(event.Severity) {
		return false
	}

	// Check event type filters
	switch event.Type {
	case EventBackupCompleted, EventRestoreCompleted, EventCleanupCompleted, EventVerifyCompleted:
		return m.config.OnSuccess
	case EventBackupFailed, EventRestoreFailed, EventVerifyFailed:
		return m.config.OnFailure
	case EventBackupStarted, EventRestoreStarted:
		return m.config.OnSuccess
	default:
		return true
	}
}

// meetsSeverity checks if event severity meets minimum threshold
func (m *Manager) meetsSeverity(severity Severity) bool {
	severityOrder := map[Severity]int{
		SeverityInfo:     0,
		SeverityWarning:  1,
		SeverityError:    2,
		SeverityCritical: 3,
	}

	eventLevel, ok := severityOrder[severity]
	if !ok {
		return true
	}

	minLevel, ok := severityOrder[m.config.MinSeverity]
	if !ok {
		return true
	}

	return eventLevel >= minLevel
}

// HasEnabledNotifiers returns true if at least one notifier is enabled
func (m *Manager) HasEnabledNotifiers() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()

	for _, n := range m.notifiers {
		if n.IsEnabled() {
			return true
		}
	}
	return false
}

// EnabledNotifiers returns the names of all enabled notifiers
func (m *Manager) EnabledNotifiers() []string {
	m.mu.RLock()
	defer m.mu.RUnlock()

	names := make([]string, 0)
	for _, n := range m.notifiers {
		if n.IsEnabled() {
			names = append(names, n.Name())
		}
	}
	return names
}

// BackupStarted sends a backup started notification
func (m *Manager) BackupStarted(database string) {
	event := NewEvent(EventBackupStarted, SeverityInfo, fmt.Sprintf("Starting backup of database '%s'", database)).
		WithDatabase(database)
	m.Notify(event)
}

// BackupCompleted sends a backup completed notification
func (m *Manager) BackupCompleted(database, backupFile string, size int64, duration interface{}) {
	event := NewEvent(EventBackupCompleted, SeverityInfo, fmt.Sprintf("Backup of database '%s' completed successfully", database)).
		WithDatabase(database).
		WithBackupInfo(backupFile, size)

	if d, ok := duration.(interface{ Seconds() float64 }); ok {
		event.WithDetail("duration_seconds", fmt.Sprintf("%.2f", d.Seconds()))
	}

	m.Notify(event)
}

// BackupFailed sends a backup failed notification
func (m *Manager) BackupFailed(database string, err error) {
	event := NewEvent(EventBackupFailed, SeverityError, fmt.Sprintf("Backup of database '%s' failed", database)).
		WithDatabase(database).
		WithError(err)
	m.Notify(event)
}

// RestoreStarted sends a restore started notification
func (m *Manager) RestoreStarted(database, backupFile string) {
	event := NewEvent(EventRestoreStarted, SeverityInfo, fmt.Sprintf("Starting restore of database '%s' from '%s'", database, backupFile)).
		WithDatabase(database).
		WithBackupInfo(backupFile, 0)
	m.Notify(event)
}

// RestoreCompleted sends a restore completed notification
func (m *Manager) RestoreCompleted(database, backupFile string, duration interface{}) {
	event := NewEvent(EventRestoreCompleted, SeverityInfo, fmt.Sprintf("Restore of database '%s' completed successfully", database)).
		WithDatabase(database).
		WithBackupInfo(backupFile, 0)

	if d, ok := duration.(interface{ Seconds() float64 }); ok {
		event.WithDetail("duration_seconds", fmt.Sprintf("%.2f", d.Seconds()))
	}

	m.Notify(event)
}

// RestoreFailed sends a restore failed notification
func (m *Manager) RestoreFailed(database string, err error) {
	event := NewEvent(EventRestoreFailed, SeverityError, fmt.Sprintf("Restore of database '%s' failed", database)).
		WithDatabase(database).
		WithError(err)
	m.Notify(event)
}

// CleanupCompleted sends a cleanup completed notification
func (m *Manager) CleanupCompleted(directory string, deleted int, spaceFreed int64) {
	event := NewEvent(EventCleanupCompleted, SeverityInfo, fmt.Sprintf("Cleanup completed: %d backups deleted", deleted)).
		WithDetail("directory", directory).
		WithDetail("space_freed", formatBytes(spaceFreed))
	m.Notify(event)
}

// VerifyCompleted sends a verification completed notification
func (m *Manager) VerifyCompleted(backupFile string, isValid bool) {
	if isValid {
		event := NewEvent(EventVerifyCompleted, SeverityInfo, "Backup verification passed").
			WithBackupInfo(backupFile, 0)
		m.Notify(event)
	} else {
		event := NewEvent(EventVerifyFailed, SeverityError, "Backup verification failed").
			WithBackupInfo(backupFile, 0)
		m.Notify(event)
	}
}

// PITRRecovery sends a PITR recovery notification
func (m *Manager) PITRRecovery(database, targetTime string) {
	event := NewEvent(EventPITRRecovery, SeverityInfo, fmt.Sprintf("Point-in-time recovery initiated for '%s' to %s", database, targetTime)).
		WithDatabase(database).
		WithDetail("target_time", targetTime)
	m.Notify(event)
}

// NullManager returns a no-op notification manager
func NullManager() *Manager {
	return &Manager{
		notifiers: make([]Notifier, 0),
	}
}
