package security

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"gopkg.in/yaml.v3"
)

// NewOverrideManager creates a new override manager
func NewOverrideManager(overridesPath, logPath string) (*OverrideManager, error) {
	manager := &OverrideManager{
		overridesPath: overridesPath,
		logPath:       logPath,
	}

	// Ensure directories exist
	if err := os.MkdirAll(filepath.Dir(overridesPath), 0700); err != nil {
		return nil, fmt.Errorf("failed to create overrides directory: %w", err)
	}
	if err := os.MkdirAll(filepath.Dir(logPath), 0700); err != nil {
		return nil, fmt.Errorf("failed to create log directory: %w", err)
	}

	// Load existing overrides
	if err := manager.loadOverrides(); err != nil {
		return nil, fmt.Errorf("failed to load overrides: %w", err)
	}

	return manager, nil
}

// loadOverrides loads the override configuration from file
func (o *OverrideManager) loadOverrides() error {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	// Check if file exists
	if _, err := os.Stat(o.overridesPath); os.IsNotExist(err) {
		// Create empty override config
		o.overrides = &OverrideConfig{
			Version: "1.0",
			Metadata: OverrideMetadata{
				Description: "Security override decisions",
				Note:        "Generated by security_override tool. Manual edits may be lost.",
			},
			Overrides: make(map[string]SecurityOverride),
			Allowlist: AllowlistPatterns{
				FilePaths: []string{},
				Domains:   []string{},
				Commands:  []string{},
			},
		}
		return o.saveOverrides()
	}

	// Read existing file
	data, err := os.ReadFile(o.overridesPath)
	if err != nil {
		return fmt.Errorf("failed to read overrides file: %w", err)
	}

	// Parse YAML
	var config OverrideConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return fmt.Errorf("failed to parse overrides YAML: %w", err)
	}

	o.overrides = &config
	return nil
}

// saveOverrides saves the override configuration to file
func (o *OverrideManager) saveOverrides() error {
	data, err := yaml.Marshal(o.overrides)
	if err != nil {
		return fmt.Errorf("failed to marshal overrides: %w", err)
	}

	if err := os.WriteFile(o.overridesPath, data, 0600); err != nil {
		return fmt.Errorf("failed to write overrides file: %w", err)
	}

	return nil
}

// SaveOverride saves a new security override
func (o *OverrideManager) SaveOverride(override SecurityOverride, securityID string) error {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	// Add to overrides map
	o.overrides.Overrides[securityID] = override

	// If allowlist action, add to allowlist patterns
	if override.Action == "allowlist" {
		o.addToAllowlist(override)
	}

	// Save to file
	return o.saveOverrides()
}

// addToAllowlist adds patterns to the allowlist based on override
func (o *OverrideManager) addToAllowlist(override SecurityOverride) {
	// Add pattern to appropriate allowlist based on type
	switch override.Type {
	case "file_access_warning", "file_pattern":
		o.overrides.Allowlist.FilePaths = append(o.overrides.Allowlist.FilePaths, override.OriginalPattern)
	case "domain_warning", "domain_pattern":
		o.overrides.Allowlist.Domains = append(o.overrides.Allowlist.Domains, override.OriginalPattern)
	case "command_warning", "shell_execution":
		o.overrides.Allowlist.Commands = append(o.overrides.Allowlist.Commands, override.OriginalPattern)
	}
}

// IsOverridden checks if a security ID or pattern is overridden
func (o *OverrideManager) IsOverridden(securityID string, pattern string, source string) bool {
	o.mutex.RLock()
	defer o.mutex.RUnlock()

	// Check direct override by security ID
	if _, exists := o.overrides.Overrides[securityID]; exists {
		return true
	}

	// Check allowlist patterns
	return o.matchesAllowlist(pattern, source)
}

// matchesAllowlist checks if pattern matches any allowlist entry
func (o *OverrideManager) matchesAllowlist(pattern, source string) bool {
	// Check file path allowlist
	for _, allowedPath := range o.overrides.Allowlist.FilePaths {
		if strings.Contains(pattern, allowedPath) || strings.Contains(source, allowedPath) {
			return true
		}
	}

	// Check domain allowlist
	for _, allowedDomain := range o.overrides.Allowlist.Domains {
		if strings.Contains(pattern, allowedDomain) || strings.Contains(source, allowedDomain) {
			return true
		}
	}

	// Check command allowlist
	for _, allowedCmd := range o.overrides.Allowlist.Commands {
		if strings.Contains(pattern, allowedCmd) {
			return true
		}
	}

	return false
}

// FindSecurityLogEntry finds a security log entry by ID
func (o *OverrideManager) FindSecurityLogEntry(securityID string) (*SecurityLogEntry, error) {
	// Read security log file
	if _, err := os.Stat(o.logPath); os.IsNotExist(err) {
		return nil, fmt.Errorf("security log file does not exist")
	}

	file, err := os.Open(o.logPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open security log: %w", err)
	}
	defer func() {
		if closeErr := file.Close(); closeErr != nil {
			// Log error but don't fail the operation
			_ = closeErr
		}
	}()

	// Parse JSONL format (one JSON object per line)
	decoder := json.NewDecoder(file)
	for decoder.More() {
		var entry SecurityLogEntry
		if err := decoder.Decode(&entry); err != nil {
			continue // Skip malformed entries
		}

		if entry.ID == securityID {
			return &entry, nil
		}
	}

	return nil, fmt.Errorf("security log entry not found")
}

// LogSecurityEvent logs a security event
func (o *OverrideManager) LogSecurityEvent(securityID, action string, analysis *ThreatAnalysis, source, tool string) error {
	entry := SecurityLogEntry{
		ID:        securityID,
		Timestamp: time.Now().Format(time.RFC3339),
		Tool:      tool,
		Source:    source,
		Type:      "content_analysis_" + action,
		Action:    action,
		Analysis:  analysis,
	}

	// Ensure log directory exists
	if err := os.MkdirAll(filepath.Dir(o.logPath), 0700); err != nil {
		return fmt.Errorf("failed to create log directory: %w", err)
	}

	// Append to log file in JSONL format
	file, err := os.OpenFile(o.logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		return fmt.Errorf("failed to open log file: %w", err)
	}
	defer func() {
		if closeErr := file.Close(); closeErr != nil {
			// Log error but don't fail the operation
			_ = closeErr
		}
	}()

	encoder := json.NewEncoder(file)
	if err := encoder.Encode(entry); err != nil {
		return fmt.Errorf("failed to write log entry: %w", err)
	}

	return nil
}

// GetOverrideStats returns statistics about overrides
func (o *OverrideManager) GetOverrideStats() map[string]any {
	o.mutex.RLock()
	defer o.mutex.RUnlock()

	bypassCount := 0
	allowlistCount := 0
	for _, override := range o.overrides.Overrides {
		switch override.Action {
		case "bypass":
			bypassCount++
		case "allowlist":
			allowlistCount++
		}
	}

	return map[string]any{
		"total_overrides":      len(o.overrides.Overrides),
		"bypass_overrides":     bypassCount,
		"allowlist_overrides":  allowlistCount,
		"allowlist_file_paths": len(o.overrides.Allowlist.FilePaths),
		"allowlist_domains":    len(o.overrides.Allowlist.Domains),
		"allowlist_commands":   len(o.overrides.Allowlist.Commands),
	}
}

// CleanupOldOverrides removes overrides older than specified duration
func (o *OverrideManager) CleanupOldOverrides(maxAge time.Duration) error {
	o.mutex.Lock()
	defer o.mutex.Unlock()

	cutoff := time.Now().Add(-maxAge)
	updated := false

	for id, override := range o.overrides.Overrides {
		if override.CreatedAt.Before(cutoff) {
			delete(o.overrides.Overrides, id)
			updated = true
		}
	}

	if updated {
		return o.saveOverrides()
	}

	return nil
}

// ExportOverrides exports overrides to a different format
func (o *OverrideManager) ExportOverrides() ([]byte, error) {
	o.mutex.RLock()
	defer o.mutex.RUnlock()

	return json.MarshalIndent(o.overrides, "", "  ")
}

// Global logging function
func LogSecurityEvent(securityID, action string, analysis *ThreatAnalysis, source, tool string) {
	if GlobalSecurityManager != nil && GlobalSecurityManager.overrides != nil {
		if err := GlobalSecurityManager.overrides.LogSecurityEvent(securityID, action, analysis, source, tool); err != nil {
			// Log error but don't fail the operation
			_ = err
		}
	}
}
