// Package security provides security monitoring and anomaly detection
package security

import (
	"fmt"
	"sync"
	"time"
)

// AlertSeverity represents the severity of a security alert
type AlertSeverity string

const (
	SeverityLow      AlertSeverity = "low"
	SeverityMedium   AlertSeverity = "medium"
	SeverityHigh     AlertSeverity = "high"
	SeverityCritical AlertSeverity = "critical"
)

// AlertType represents the type of security alert
type AlertType string

const (
	AlertBruteForce          AlertType = "brute_force"
	AlertPrivilegeEscalation AlertType = "privilege_escalation"
	AlertPathTraversal       AlertType = "path_traversal"
	AlertCommandInjection    AlertType = "command_injection"
	AlertSuspiciousContainer AlertType = "suspicious_container"
	AlertRateLimitExceeded   AlertType = "rate_limit_exceeded"
)

// Alert represents a security alert
type Alert struct {
	Timestamp time.Time      `json:"timestamp"`
	Metadata  map[string]any `json:"metadata,omitempty"`
	Severity  AlertSeverity  `json:"severity"`
	Type      AlertType      `json:"type"`
	Message   string         `json:"message"`
	SourceIP  string         `json:"source_ip,omitempty"`
	UserID    string         `json:"user_id,omitempty"`
}

// AlertHandler is called when a security alert is generated
type AlertHandler func(alert Alert)

// SlidingWindow tracks events in a time window
type SlidingWindow struct {
	events []time.Time
	window time.Duration
	mu     sync.RWMutex
}

// NewSlidingWindow creates a new sliding window
func NewSlidingWindow(window time.Duration) *SlidingWindow {
	return &SlidingWindow{
		events: make([]time.Time, 0),
		window: window,
	}
}

// Add adds an event to the window
func (w *SlidingWindow) Add(t time.Time) {
	w.mu.Lock()
	defer w.mu.Unlock()

	// Remove old events outside the window
	cutoff := t.Add(-w.window)
	newEvents := make([]time.Time, 0, len(w.events)+1)
	for _, e := range w.events {
		if e.After(cutoff) {
			newEvents = append(newEvents, e)
		}
	}
	newEvents = append(newEvents, t)
	w.events = newEvents
}

// Count returns the number of events in the window
func (w *SlidingWindow) Count() int {
	w.mu.RLock()
	defer w.mu.RUnlock()

	// Clean up old events
	cutoff := time.Now().Add(-w.window)
	count := 0
	for _, e := range w.events {
		if e.After(cutoff) {
			count++
		}
	}
	return count
}

// AnomalyMonitor tracks security-relevant events and generates alerts
type AnomalyMonitor struct {
	lastPrivilegedAlert         time.Time
	failedAuthByIP              map[string]*SlidingWindow
	alertHandler                AlertHandler
	privilegedContainerAttempts int
	pathTraversalAttempts       int
	commandInjectionAttempts    int
	bruteForceThreshold         int
	bruteForceWindow            time.Duration
	privilegedAlertInterval     time.Duration
	mu                          sync.RWMutex
}

// NewAnomalyMonitor creates a new security anomaly monitor
func NewAnomalyMonitor(alertHandler AlertHandler) *AnomalyMonitor {
	return &AnomalyMonitor{
		failedAuthByIP:          make(map[string]*SlidingWindow),
		alertHandler:            alertHandler,
		bruteForceThreshold:     10,              // 10 failed attempts
		bruteForceWindow:        5 * time.Minute, // in 5 minutes
		privilegedAlertInterval: 1 * time.Minute, // max 1 alert per minute
	}
}

// RecordFailedAuth records a failed authentication attempt
func (m *AnomalyMonitor) RecordFailedAuth(ip, userID string) {
	m.mu.Lock()
	window, exists := m.failedAuthByIP[ip]
	if !exists {
		window = NewSlidingWindow(m.bruteForceWindow)
		m.failedAuthByIP[ip] = window
	}
	m.mu.Unlock()

	window.Add(time.Now())
	count := window.Count()

	if count >= m.bruteForceThreshold {
		m.alert(Alert{
			Severity:  SeverityHigh,
			Type:      AlertBruteForce,
			Message:   fmt.Sprintf("%d+ failed auth attempts from %s", m.bruteForceThreshold, ip),
			Timestamp: time.Now(),
			SourceIP:  ip,
			UserID:    userID,
			Metadata: map[string]any{
				"count":          count,
				"threshold":      m.bruteForceThreshold,
				"window_seconds": m.bruteForceWindow.Seconds(),
			},
		})
	}
}

// RecordPrivilegedContainerAttempt records a blocked privileged container request
func (m *AnomalyMonitor) RecordPrivilegedContainerAttempt(userID string) {
	m.mu.Lock()
	m.privilegedContainerAttempts++
	now := time.Now()
	shouldAlert := now.Sub(m.lastPrivilegedAlert) > m.privilegedAlertInterval
	if shouldAlert {
		m.lastPrivilegedAlert = now
	}
	m.mu.Unlock()

	if shouldAlert {
		m.alert(Alert{
			Severity:  SeverityCritical,
			Type:      AlertPrivilegeEscalation,
			Message:   "Attempted to create privileged container",
			Timestamp: time.Now(),
			UserID:    userID,
			Metadata: map[string]any{
				"total_attempts": m.privilegedContainerAttempts,
			},
		})
	}
}

// RecordPathTraversal records a path traversal attempt
func (m *AnomalyMonitor) RecordPathTraversal(ip, path string) {
	m.mu.Lock()
	m.pathTraversalAttempts++
	m.mu.Unlock()

	m.alert(Alert{
		Severity:  SeverityHigh,
		Type:      AlertPathTraversal,
		Message:   "Path traversal attempt detected",
		Timestamp: time.Now(),
		SourceIP:  ip,
		Metadata: map[string]any{
			"path":           path,
			"total_attempts": m.pathTraversalAttempts,
		},
	})
}

// RecordCommandInjection records a command injection attempt
func (m *AnomalyMonitor) RecordCommandInjection(ip, input string) {
	m.mu.Lock()
	m.commandInjectionAttempts++
	m.mu.Unlock()

	m.alert(Alert{
		Severity:  SeverityCritical,
		Type:      AlertCommandInjection,
		Message:   "Command injection attempt detected",
		Timestamp: time.Now(),
		SourceIP:  ip,
		Metadata: map[string]any{
			"input":          input,
			"total_attempts": m.commandInjectionAttempts,
		},
	})
}

// GetStats returns current monitoring statistics
func (m *AnomalyMonitor) GetStats() map[string]int {
	m.mu.RLock()
	defer m.mu.RUnlock()

	return map[string]int{
		"privileged_container_attempts": m.privilegedContainerAttempts,
		"path_traversal_attempts":       m.pathTraversalAttempts,
		"command_injection_attempts":    m.commandInjectionAttempts,
		"monitored_ips":                 len(m.failedAuthByIP),
	}
}

// alert sends an alert through the handler
func (m *AnomalyMonitor) alert(alert Alert) {
	if m.alertHandler != nil {
		m.alertHandler(alert)
	}
}

// DefaultAlertHandler logs alerts to stderr
func DefaultAlertHandler(alert Alert) {
	fmt.Printf("[SECURITY ALERT] %s | %s | %s | %s\n",
		alert.Timestamp.Format(time.RFC3339),
		alert.Severity,
		alert.Type,
		alert.Message,
	)
}

// LoggingAlertHandler creates an alert handler that logs via a structured logger
type LoggingAlertHandler struct {
	logFunc func(string, ...any)
}

// NewLoggingAlertHandler creates a new logging alert handler
func NewLoggingAlertHandler(logFunc func(string, ...any)) AlertHandler {
	return func(alert Alert) {
		logFunc("security_alert",
			"severity", alert.Severity,
			"type", alert.Type,
			"message", alert.Message,
			"source_ip", alert.SourceIP,
			"user_id", alert.UserID,
		)
	}
}

// Integration Example:
//
// To integrate the anomaly monitor with your application:
//
// 1. Create a monitor with a logging handler:
//    monitor := security.NewAnomalyMonitor(security.DefaultAlertHandler)
//
// 2. Wire into authentication middleware:
//    func authMiddleware(next http.Handler) http.Handler {
//        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//            key := r.Header.Get("X-API-Key")
//            user, err := validateAPIKey(key)
//            if err != nil {
//                monitor.RecordFailedAuth(r.RemoteAddr, "")
//                http.Error(w, "Unauthorized", 401)
//                return
//            }
//            next.ServeHTTP(w, r)
//        })
//    }
//
// 3. Wire into container creation:
//    func createContainer(config ContainerConfig) error {
//        if config.Privileged {
//            monitor.RecordPrivilegedContainerAttempt(userID)
//            return fmt.Errorf("privileged containers not allowed")
//        }
//        // ... create container
//    }
//
// 4. Wire into input validation:
//    func validateJobName(name string) error {
//        if strings.Contains(name, "..") {
//            monitor.RecordPathTraversal(ip, name)
//            return fmt.Errorf("invalid job name")
//        }
//        // ... continue validation
//    }
//
// 5. Periodically check stats:
//    stats := monitor.GetStats()
//    log.Printf("Security stats: %+v", stats)
