package eventlog

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

	"github.com/safedep/dry/log"
	"github.com/safedep/pmg/config"
)

// EventType represents the type of event being logged
type EventType string

const (
	EventTypeMalwareBlocked        EventType = "malware_blocked"
	EventTypeMalwareConfirmed      EventType = "malware_confirmed"
	EventTypeInstallAllowed        EventType = "install_allowed"
	EventTypeInstallTrustedAllowed EventType = "install_trusted_allowed"
	EventTypeInstallStarted        EventType = "install_started"
	EventTypeDependencyResolved    EventType = "dependency_resolved"
	EventTypeInstallInsecureBypass EventType = "install_insecure_bypass"
	EventTypeProxyHostObserved     EventType = "proxy_host_observed"
	EventTypeSandboxOverride       EventType = "sandbox_override"
	EventTypeError                 EventType = "error"
)

// Event represents a security event
type Event struct {
	Timestamp   time.Time              `json:"timestamp"`
	EventType   EventType              `json:"event_type"`
	Message     string                 `json:"message"`
	PackageName string                 `json:"package_name,omitempty"`
	Version     string                 `json:"version,omitempty"`
	Ecosystem   string                 `json:"ecosystem,omitempty"`
	Details     map[string]interface{} `json:"details,omitempty"`
}

// Logger defines the contract for implementing event loggers.
type Logger interface {
	// Log writes an event to the log file
	Log(event Event) error

	// Close closes the logger
	Close() error

	// IsActive returns whether the logger is active
	IsActive() bool
}

// fileWithRotationLogger represents an event logger that writes to a file and rotates the
// file when it reaches a certain age
type fileWithRotationLogger struct {
	file   *os.File
	writer io.Writer
	mu     sync.Mutex
	active bool
}

// fileWithRotationLogger implements the Logger interface. This is the default logger
// that will be used. Future enhancements will introduce additional and optional loggers.
var _ Logger = &fileWithRotationLogger{}

var (
	globalLogger Logger
	once         sync.Once
)

// GetDefaultLogDir returns the default log directory based on the OS
func GetDefaultLogDir() (string, error) {
	return config.Get().EventLogDir(), nil
}

// Initialize sets up the global event logger with the default log directory
func Initialize() error {
	if config.Get().Config.SkipEventLogging {
		return nil
	}

	logDir, err := GetDefaultLogDir()
	if err != nil {
		return fmt.Errorf("failed to get default log directory: %w", err)
	}

	return InitializeWithDir(logDir)
}

// InitializeWithFile sets up the global event logger with a specific file path
func InitializeWithFile(filePath string) error {
	if config.Get().Config.SkipEventLogging {
		return nil
	}

	var initErr error
	once.Do(func() {
		fwrl := &fileWithRotationLogger{}
		initErr = fwrl.initWithFile(filePath)
		globalLogger = fwrl
	})

	return initErr
}

// InitializeWithDir sets up the global event logger with a custom log directory
func InitializeWithDir(logDir string) error {
	if config.Get().Config.SkipEventLogging {
		return nil
	}

	var initErr error
	once.Do(func() {
		fwrl := &fileWithRotationLogger{}
		initErr = fwrl.init(logDir)
		globalLogger = fwrl
	})

	return initErr
}

// reinitializeForTest resets and reinitializes the logger for testing purposes
// This should only be used in tests
func reinitializeForTest(logDir string) error {
	// Close existing logger if any
	if globalLogger != nil {
		globalLogger.Close()
	}

	// Reset once
	once = sync.Once{}

	// Initialize new logger
	return InitializeWithDir(logDir)
}

// init initializes the logger with the specified directory
func (l *fileWithRotationLogger) init(logDir string) error {
	// Create log directory if it doesn't exist
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return fmt.Errorf("failed to create log directory: %w", err)
	}

	// Create log file with timestamp-based naming
	logFileName := time.Now().Format("20060102") + "-pmg.log"
	logFilePath := filepath.Join(logDir, logFileName)

	file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return fmt.Errorf("failed to open log file: %w", err)
	}

	l.file = file
	l.writer = file
	l.active = true

	// Clean up old logs in background
	go l.cleanupOldLogs(logDir)

	return nil
}

// initWithFile initializes the logger with a specific file path
func (l *fileWithRotationLogger) initWithFile(filePath string) error {
	// Create directory if it doesn't exist
	dir := filepath.Dir(filePath)
	if dir != "" && dir != "." {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("failed to create log directory: %w", err)
		}
	}

	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return fmt.Errorf("failed to open log file: %w", err)
	}

	l.file = file
	l.writer = file
	l.active = true

	// No cleanup needed for custom log files (user manages them)

	return nil
}

// cleanupOldLogs removes log files older than 7 days
func (l *fileWithRotationLogger) cleanupOldLogs(logDir string) {
	cutoff := time.Now().AddDate(0, 0, -1*config.Get().Config.EventLogRetentionDays)

	entries, err := os.ReadDir(logDir)
	if err != nil {
		log.Warnf("Failed to read log directory for cleanup: %v", err)
		return
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		// Only process *-pmg.log files
		name := entry.Name()
		matched, err := filepath.Match("*-pmg.log", name)
		if err != nil || !matched {
			continue
		}

		filePath := filepath.Join(logDir, name)
		info, err := entry.Info()
		if err != nil {
			log.Warnf("Failed to get info for log file: %v", err)
			continue
		}

		if info.ModTime().Before(cutoff) {
			if err := os.Remove(filePath); err != nil {
				log.Warnf("Failed to remove old log file: %v", err)
			}
		}
	}
}

// Log writes an event to the log file
func (l *fileWithRotationLogger) Log(event Event) error {
	if !l.active {
		return nil
	}

	l.mu.Lock()
	defer l.mu.Unlock()

	// Set timestamp if not already set
	if event.Timestamp.IsZero() {
		event.Timestamp = time.Now()
	}

	// Marshal event to JSON
	data, err := json.Marshal(event)
	if err != nil {
		return fmt.Errorf("failed to marshal event: %w", err)
	}

	// Write to file
	_, err = l.writer.Write(append(data, '\n'))
	if err != nil {
		return fmt.Errorf("failed to write event: %w", err)
	}

	// Flush to ensure data is written
	if l.file != nil {
		if err := l.file.Sync(); err != nil {
			return fmt.Errorf("failed to sync file: %w", err)
		}
	}

	return nil
}

// Close closes the logger
func (l *fileWithRotationLogger) Close() error {
	if !l.active {
		return nil
	}

	l.mu.Lock()
	defer l.mu.Unlock()

	l.active = false
	if l.file != nil {
		return l.file.Close()
	}

	return nil
}

// IsActive returns whether the logger is active
func (l *fileWithRotationLogger) IsActive() bool {
	return l.active
}

// Global logging functions

// LogEvent logs an event using the global logger
func LogEvent(event Event) error {
	// If logger is not initialized or not active, silently fail
	if globalLogger == nil || !globalLogger.IsActive() {
		return nil
	}

	return globalLogger.Log(event)
}

// LogMalwareBlocked logs when malware is blocked
func LogMalwareBlocked(packageName, version, ecosystem, reason string, details map[string]interface{}) {
	event := Event{
		EventType:   EventTypeMalwareBlocked,
		Message:     fmt.Sprintf("Blocked installation of malicious package: %s@%s", packageName, version),
		PackageName: packageName,
		Version:     version,
		Ecosystem:   ecosystem,
		Details:     details,
	}

	if details == nil {
		event.Details = make(map[string]interface{})
	}

	event.Details["reason"] = reason
	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log malware blocked event: %s", err)
	}
}

// LogMalwareConfirmed logs when user confirms installation despite warning
func LogMalwareConfirmed(packageName, version, ecosystem string) {
	event := Event{
		EventType:   EventTypeMalwareConfirmed,
		Message:     fmt.Sprintf("User confirmed installation of flagged package: %s@%s", packageName, version),
		PackageName: packageName,
		Version:     version,
		Ecosystem:   ecosystem,
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log malware confirmed event: %s", err)
	}
}

// LogInstallAllowed logs when an installation is allowed
func LogInstallAllowed(packageName, version, ecosystem string, packageCount int) {
	event := Event{
		EventType:   EventTypeInstallAllowed,
		Message:     fmt.Sprintf("Installation allowed for %s@%s (%d packages analyzed)", packageName, version, packageCount),
		PackageName: packageName,
		Version:     version,
		Ecosystem:   ecosystem,
		Details: map[string]interface{}{
			"packages_analyzed": packageCount,
		},
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log install allowed event: %s", err)
	}
}

// LogInstallTrustedAllowed logs when an installation is allowed for a trusted package
func LogInstallTrustedAllowed(packageName, version, ecosystem string) {
	event := Event{
		EventType:   EventTypeInstallTrustedAllowed,
		Message:     fmt.Sprintf("Installation allowed for trusted package: %s@%s", packageName, version),
		PackageName: packageName,
		Version:     version,
		Ecosystem:   ecosystem,
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log install trusted allowed event: %s", err)
	}
}

// LogInstallInsecureBypass logs when an installation skips analysis due to insecure installation mode.
func LogInstallInsecureBypass(packageName, version, ecosystem string) {
	event := Event{
		EventType:   EventTypeInstallInsecureBypass,
		Message:     fmt.Sprintf("Installation bypassed analysis due to insecure installation mode: %s@%s", packageName, version),
		PackageName: packageName,
		Version:     version,
		Ecosystem:   ecosystem,
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log install insecure bypass event: %s", err)
	}
}

// LogInstallStarted logs when an installation starts
func LogInstallStarted(packageManager string, args []string) {
	event := Event{
		EventType: EventTypeInstallStarted,
		Message:   fmt.Sprintf("Starting package installation with %s", packageManager),
		Details: map[string]interface{}{
			"package_manager": packageManager,
			"arguments":       args,
		},
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log install started event: %s", err)
	}
}

// LogProxyHostObserved logs when proxy mode observes outbound traffic to a host.
func LogProxyHostObserved(hostname, method, reason string, details map[string]interface{}) {
	event := Event{
		EventType: EventTypeProxyHostObserved,
		Message:   fmt.Sprintf("Proxy observed outbound host: %s", hostname),
		Details: map[string]interface{}{
			"hostname": hostname,
			"method":   method,
			"reason":   reason,
		},
	}

	for k, v := range details {
		event.Details[k] = v
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log proxy host observed event: %s", err)
	}
}

// LogSandboxOverrides logs when runtime sandbox allow overrides are applied.
func LogSandboxOverrides(sandboxProfile string, overrides []map[string]string) {
	event := Event{
		EventType: EventTypeSandboxOverride,
		Message:   fmt.Sprintf("Sandbox runtime overrides applied (%d rules)", len(overrides)),
		Details: map[string]interface{}{
			"sandbox_profile":          sandboxProfile,
			"sandbox_runtime_overrides": overrides,
		},
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log sandbox override event: %s", err)
	}
}

// LogError logs an error event
func LogError(message string, err error) {
	event := Event{
		EventType: EventTypeError,
		Message:   message,
		Details: map[string]interface{}{
			"error": err.Error(),
		},
	}

	if err := LogEvent(event); err != nil {
		log.Warnf("failed to log error event: %s", err)
	}
}

// Close closes the global logger
func Close() error {
	if globalLogger != nil {
		return globalLogger.Close()
	}

	return nil
}
