package logging

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/Fimeg/RedFlag/aggregator-server/internal/models"
	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
	"gopkg.in/natefinch/lumberjack.v2"
)

// SecurityLogConfig holds configuration for security logging
type SecurityLogConfig struct {
	Enabled           bool   `yaml:"enabled" env:"REDFLAG_SECURITY_LOG_ENABLED" default:"true"`
	Level             string `yaml:"level" env:"REDFLAG_SECURITY_LOG_LEVEL" default:"warning"` // none, error, warn, info, debug
	LogSuccesses      bool   `yaml:"log_successes" env:"REDFLAG_SECURITY_LOG_SUCCESSES" default:"false"`
	FilePath          string `yaml:"file_path" env:"REDFLAG_SECURITY_LOG_PATH" default:"/var/log/redflag/security.json"`
	MaxSizeMB         int    `yaml:"max_size_mb" env:"REDFLAG_SECURITY_LOG_MAX_SIZE" default:"100"`
	MaxFiles          int    `yaml:"max_files" env:"REDFLAG_SECURITY_LOG_MAX_FILES" default:"10"`
	RetentionDays     int    `yaml:"retention_days" env:"REDFLAG_SECURITY_LOG_RETENTION" default:"90"`
	LogToDatabase     bool   `yaml:"log_to_database" env:"REDFLAG_SECURITY_LOG_TO_DB" default:"true"`
	HashIPAddresses   bool   `yaml:"hash_ip_addresses" env:"REDFLAG_SECURITY_LOG_HASH_IP" default:"true"`
}

// SecurityLogger handles structured security event logging
type SecurityLogger struct {
	config    SecurityLogConfig
	logger    *log.Logger
	db        *sqlx.DB
	lumberjack *lumberjack.Logger
	mu        sync.RWMutex
	buffer    chan *models.SecurityEvent
	bufferSize int
	stopChan   chan struct{}
	wg         sync.WaitGroup
}

// NewSecurityLogger creates a new security logger instance
func NewSecurityLogger(config SecurityLogConfig, db *sqlx.DB) (*SecurityLogger, error) {
	if !config.Enabled || config.Level == "none" {
		return &SecurityLogger{
			config: config,
			logger: log.New(os.Stdout, "[SECURITY] ", log.LstdFlags|log.LUTC),
		}, nil
	}

	// Ensure log directory exists
	logDir := filepath.Dir(config.FilePath)
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create security log directory: %w", err)
	}

	// Setup rotating file writer
	lumberjack := &lumberjack.Logger{
		Filename:   config.FilePath,
		MaxSize:    config.MaxSizeMB,
		MaxBackups: config.MaxFiles,
		MaxAge:     config.RetentionDays,
		Compress:   true,
	}

	logger := &SecurityLogger{
		config:     config,
		logger:     log.New(lumberjack, "", 0), // No prefix, we'll add timestamps ourselves
		db:         db,
		lumberjack: lumberjack,
		buffer:     make(chan *models.SecurityEvent, 1000),
		bufferSize: 1000,
		stopChan:   make(chan struct{}),
	}

	// Start background processor
	logger.wg.Add(1)
	go logger.processEvents()

	return logger, nil
}

// Log writes a security event
func (sl *SecurityLogger) Log(event *models.SecurityEvent) error {
	if !sl.config.Enabled || sl.config.Level == "none" {
		return nil
	}

	// Skip successes unless configured to log them
	if !sl.config.LogSuccesses && event.EventType == models.SecurityEventTypes.CmdSignatureVerificationSuccess {
		return nil
	}

	// Filter by log level
	if !sl.shouldLogLevel(event.Level) {
		return nil
	}

	// Hash IP addresses if configured
	if sl.config.HashIPAddresses && event.IPAddress != "" {
		event.HashIPAddress()
	}

	// Try to send to buffer (non-blocking)
	select {
	case sl.buffer <- event:
	default:
		// Buffer full, log directly synchronously
		return sl.writeEvent(event)
	}

	return nil
}

// LogCommandVerificationFailure logs a command signature verification failure
func (sl *SecurityLogger) LogCommandVerificationFailure(agentID, commandID uuid.UUID, reason string) {
	event := models.NewSecurityEvent("CRITICAL", models.SecurityEventTypes.CmdSignatureVerificationFailed, agentID, "Command signature verification failed")
	event.WithDetail("command_id", commandID.String())
	event.WithDetail("reason", reason)

	_ = sl.Log(event)
}

// LogUpdateSignatureValidationFailure logs an update signature validation failure
func (sl *SecurityLogger) LogUpdateSignatureValidationFailure(agentID uuid.UUID, updateID string, reason string) {
	event := models.NewSecurityEvent("CRITICAL", models.SecurityEventTypes.UpdateSignatureVerificationFailed, agentID, "Update signature validation failed")
	event.WithDetail("update_id", updateID)
	event.WithDetail("reason", reason)

	_ = sl.Log(event)
}

// LogCommandSigned logs successful command signing
func (sl *SecurityLogger) LogCommandSigned(cmd *models.AgentCommand) {
	event := models.NewSecurityEvent("INFO", models.SecurityEventTypes.CmdSigned, cmd.AgentID, "Command signed successfully")
	event.WithDetail("command_id", cmd.ID.String())
	event.WithDetail("command_type", cmd.CommandType)
	event.WithDetail("signature_present", cmd.Signature != "")

	_ = sl.Log(event)
}

// LogNonceValidationFailure logs a nonce validation failure
func (sl *SecurityLogger) LogNonceValidationFailure(agentID uuid.UUID, nonce string, reason string) {
	event := models.NewSecurityEvent("WARNING", models.SecurityEventTypes.UpdateNonceInvalid, agentID, "Update nonce validation failed")
	event.WithDetail("nonce", nonce)
	event.WithDetail("reason", reason)

	_ = sl.Log(event)
}

// LogMachineIDMismatch logs a machine ID mismatch
func (sl *SecurityLogger) LogMachineIDMismatch(agentID uuid.UUID, expected, actual string) {
	event := models.NewSecurityEvent("WARNING", models.SecurityEventTypes.MachineIDMismatch, agentID, "Machine ID mismatch detected")
	event.WithDetail("expected_machine_id", expected)
	event.WithDetail("actual_machine_id", actual)

	_ = sl.Log(event)
}

// LogAuthJWTValidationFailure logs a JWT validation failure
func (sl *SecurityLogger) LogAuthJWTValidationFailure(agentID uuid.UUID, token string, reason string) {
	event := models.NewSecurityEvent("WARNING", models.SecurityEventTypes.AuthJWTValidationFailed, agentID, "JWT authentication failed")
	event.WithDetail("reason", reason)
	if len(token) > 0 {
		event.WithDetail("token_preview", token[:min(len(token), 20)]+"...")
	}

	_ = sl.Log(event)
}

// LogPrivateKeyNotConfigured logs when private key is not configured
func (sl *SecurityLogger) LogPrivateKeyNotConfigured() {
	event := models.NewSecurityEvent("CRITICAL", models.SecurityEventTypes.PrivateKeyNotConfigured, uuid.Nil, "Private signing key not configured")
	event.WithDetail("component", "server")

	_ = sl.Log(event)
}

// LogAgentRegistrationFailed logs an agent registration failure
func (sl *SecurityLogger) LogAgentRegistrationFailed(ip string, reason string) {
	event := models.NewSecurityEvent("WARNING", models.SecurityEventTypes.AgentRegistrationFailed, uuid.Nil, "Agent registration failed")
	event.WithIPAddress(ip)
	event.WithDetail("reason", reason)

	_ = sl.Log(event)
}

// LogUnauthorizedAccessAttempt logs an unauthorized access attempt
func (sl *SecurityLogger) LogUnauthorizedAccessAttempt(ip, endpoint, reason string, agentID uuid.UUID) {
	event := models.NewSecurityEvent("WARNING", models.SecurityEventTypes.UnauthorizedAccessAttempt, agentID, "Unauthorized access attempt")
	event.WithIPAddress(ip)
	event.WithDetail("endpoint", endpoint)
	event.WithDetail("reason", reason)

	_ = sl.Log(event)
}

// processEvents processes events from the buffer in the background
func (sl *SecurityLogger) processEvents() {
	defer sl.wg.Done()

	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	batch := make([]*models.SecurityEvent, 0, 100)

	for {
		select {
		case event := <-sl.buffer:
			batch = append(batch, event)
			if len(batch) >= 100 {
				sl.processBatch(batch)
				batch = batch[:0]
			}
		case <-ticker.C:
			if len(batch) > 0 {
				sl.processBatch(batch)
				batch = batch[:0]
			}
		case <-sl.stopChan:
			// Process any remaining events
			for len(sl.buffer) > 0 {
				batch = append(batch, <-sl.buffer)
			}
			if len(batch) > 0 {
				sl.processBatch(batch)
			}
			return
		}
	}
}

// processBatch processes a batch of events
func (sl *SecurityLogger) processBatch(events []*models.SecurityEvent) {
	for _, event := range events {
		_ = sl.writeEvent(event)
	}
}

// writeEvent writes an event to the configured outputs
func (sl *SecurityLogger) writeEvent(event *models.SecurityEvent) error {
	// Write to file
	if err := sl.writeToFile(event); err != nil {
		log.Printf("[ERROR] Failed to write security event to file: %v", err)
	}

	// Write to database if configured
	if sl.config.LogToDatabase && sl.db != nil && event.ShouldLogToDatabase(sl.config.LogToDatabase) {
		if err := sl.writeToDatabase(event); err != nil {
			log.Printf("[ERROR] Failed to write security event to database: %v", err)
		}
	}

	return nil
}

// writeToFile writes the event as JSON to the log file
func (sl *SecurityLogger) writeToFile(event *models.SecurityEvent) error {
	jsonData, err := json.Marshal(event)
	if err != nil {
		return fmt.Errorf("failed to marshal security event: %w", err)
	}

	sl.logger.Println(string(jsonData))
	return nil
}

// writeToDatabase writes the event to the database
func (sl *SecurityLogger) writeToDatabase(event *models.SecurityEvent) error {
	// Create security_events table if not exists
	if err := sl.ensureSecurityEventsTable(); err != nil {
		return fmt.Errorf("failed to ensure security_events table: %w", err)
	}

	// Encode details and metadata as JSON
	detailsJSON, _ := json.Marshal(event.Details)
	metadataJSON, _ := json.Marshal(event.Metadata)

	query := `
		INSERT INTO security_events (timestamp, level, event_type, agent_id, message, trace_id, ip_address, details, metadata)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`

	_, err := sl.db.Exec(query,
		event.Timestamp,
		event.Level,
		event.EventType,
		event.AgentID,
		event.Message,
		event.TraceID,
		event.IPAddress,
		detailsJSON,
		metadataJSON,
	)

	return err
}

// ensureSecurityEventsTable creates the security_events table if it doesn't exist
func (sl *SecurityLogger) ensureSecurityEventsTable() error {
	query := `
		CREATE TABLE IF NOT EXISTS security_events (
			id SERIAL PRIMARY KEY,
			timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
			level VARCHAR(20) NOT NULL,
			event_type VARCHAR(100) NOT NULL,
			agent_id UUID,
			message TEXT NOT NULL,
			trace_id VARCHAR(100),
			ip_address VARCHAR(100),
			details JSONB,
			metadata JSONB,
			INDEX idx_security_events_timestamp (timestamp),
			INDEX idx_security_events_agent_id (agent_id),
			INDEX idx_security_events_level (level),
			INDEX idx_security_events_event_type (event_type)
		)`

	_, err := sl.db.Exec(query)
	return err
}

// Close closes the security logger and flushes any pending events
func (sl *SecurityLogger) Close() error {
	if sl.lumberjack != nil {
		close(sl.stopChan)
		sl.wg.Wait()
		if err := sl.lumberjack.Close(); err != nil {
			return err
		}
	}
	return nil
}

// shouldLogLevel checks if the event should be logged based on the configured level
func (sl *SecurityLogger) shouldLogLevel(eventLevel string) bool {
	levels := map[string]int{
		"NONE":    0,
		"ERROR":   1,
		"WARNING": 2,
		"INFO":    3,
		"DEBUG":   4,
	}

	configLevel := levels[sl.config.Level]
	eventLvl, exists := levels[eventLevel]
	if !exists {
		eventLvl = 2 // Default to WARNING
	}

	return eventLvl <= configLevel
}

// min returns the minimum of two integers
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

