package audit

import (
	"context"
	"fmt"
	"time"

	packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
	"github.com/safedep/dry/log"
	"github.com/safedep/pmg/config"
)

var global *auditor

func setGlobal(a *auditor) {
	global = a
}

func resetGlobal() {
	global = nil
}

// Initialize sets up the audit system with an eventlog sink and, when enabled,
// a cloud sync sink.
func Initialize(cfg *config.RuntimeConfig) error {
	var sinks []Sink
	sinks = append(sinks, newEventlogSink())

	if cfg.Config.Cloud.Enabled {
		cs, err := newCloudSink(cfg, newCloudSinkCIResolver())
		if err != nil {
			log.Warnf("Cloud sync initialization failed: %v", err)
		} else {
			sinks = append(sinks, cs)
		}
	}

	setGlobal(newAuditor(sinks...))
	return nil
}

func Close() error {
	if global == nil {
		return nil
	}
	return global.close()
}

func logEvent(event AuditEvent) {
	if global == nil {
		return
	}
	global.dispatch(context.Background(), event)
}

func pkgName(pv *packagev1.PackageVersion) string {
	if pv != nil {
		if pkg := pv.GetPackage(); pkg != nil {
			return pkg.GetName()
		}
	}
	return ""
}

func pkgVersion(pv *packagev1.PackageVersion) string {
	if pv != nil {
		return pv.GetVersion()
	}
	return ""
}

func pkgEcosystem(pv *packagev1.PackageVersion) string {
	if pv != nil {
		if pkg := pv.GetPackage(); pkg != nil {
			return pkg.GetEcosystem().String()
		}
	}
	return ""
}

// LogMalwareBlocked records that a package was blocked due to malware detection.
func LogMalwareBlocked(pv *packagev1.PackageVersion, reason, analysisID, referenceURL string, isMalware, isVerified bool) {
	logEvent(AuditEvent{
		Type:           EventTypeMalwareBlocked,
		Message:        fmt.Sprintf("Blocked installation of malicious package: %s@%s", pkgName(pv), pkgVersion(pv)),
		PackageVersion: pv,
		AnalysisID:     analysisID,
		IsMalware:      isMalware,
		IsVerified:     isVerified,
		Details: map[string]any{
			"reason":        reason,
			"analysis_id":   analysisID,
			"reference_url": referenceURL,
		},
	})

	if global != nil {
		global.recordBlocked()
	}
}

// LogMalwareConfirmed records that the user confirmed installation of a flagged package.
func LogMalwareConfirmed(pv *packagev1.PackageVersion, analysisID string, isMalware, isVerified bool) {
	logEvent(AuditEvent{
		Type:           EventTypeMalwareConfirmed,
		Message:        fmt.Sprintf("User confirmed installation of flagged package: %s@%s", pkgName(pv), pkgVersion(pv)),
		PackageVersion: pv,
		AnalysisID:     analysisID,
		IsMalware:      isMalware,
		IsVerified:     isVerified,
	})

	if global != nil {
		global.recordConfirmed()
	}
}

// LogInstallAllowed records that a package passed security checks and installation was permitted.
func LogInstallAllowed(pv *packagev1.PackageVersion, packageCount int) {
	logEvent(AuditEvent{
		Type:           EventTypeInstallAllowed,
		Message:        fmt.Sprintf("Installation allowed for %s@%s (%d packages analyzed)", pkgName(pv), pkgVersion(pv), packageCount),
		PackageVersion: pv,
		Details: map[string]any{
			"packages_analyzed": packageCount,
		},
		PackageCount: packageCount,
	})

	if global != nil {
		global.recordAllowed()
	}
}

// LogInstallTrustedAllowed records that a trusted package skipped security analysis.
func LogInstallTrustedAllowed(pv *packagev1.PackageVersion) {
	logEvent(AuditEvent{
		Type:           EventTypeInstallTrustedAllowed,
		Message:        fmt.Sprintf("Installation allowed for trusted package: %s@%s", pkgName(pv), pkgVersion(pv)),
		PackageVersion: pv,
	})

	if global != nil {
		global.recordTrustedSkipped()
	}
}

// LogInstallInsecureBypass records that a package bypassed security analysis due to insecure mode.
func LogInstallInsecureBypass(pv *packagev1.PackageVersion) {
	logEvent(AuditEvent{
		Type:           EventTypeInstallInsecureBypass,
		Message:        fmt.Sprintf("Installation bypassed analysis due to insecure installation mode: %s@%s", pkgName(pv), pkgVersion(pv)),
		PackageVersion: pv,
	})

	if global != nil {
		global.recordInsecureBypassed()
	}
}

// LogInstallStarted records the start of a package installation session.
func LogInstallStarted(packageManager string, args []string) {
	logEvent(AuditEvent{
		Type:    EventTypeInstallStarted,
		Message: fmt.Sprintf("Starting package installation with %s", packageManager),
		Details: map[string]any{
			"package_manager": packageManager,
			"arguments":       args,
		},
		PackageManager: packageManager,
		Args:           args,
	})

	if global != nil {
		global.startSession(packageManager, args)
	}
}

// LogProxyHostObserved records an outbound host observed by the proxy that is not a known registry.
func LogProxyHostObserved(hostname, method, reason string, details map[string]any) {
	base := map[string]any{
		"hostname": hostname,
		"method":   method,
		"reason":   reason,
	}

	logEvent(AuditEvent{
		Type:     EventTypeProxyHostObserved,
		Message:  fmt.Sprintf("Proxy observed outbound host: %s", hostname),
		Details:  mergeDetails(base, details),
		Hostname: hostname,
		Method:   method,
		Reason:   reason,
	})
}

// LogDependencyCooldown records that a package was blocked by the dependency cooldown policy.
func LogDependencyCooldown(pv *packagev1.PackageVersion, publishDate time.Time, cooldownDays, daysAgo, daysLeft int) {
	logEvent(AuditEvent{
		Type:           EventTypeDependencyCooldown,
		Message:        fmt.Sprintf("Package blocked by cooldown policy: %s@%s (published %d days ago, %d days remaining)", pkgName(pv), pkgVersion(pv), daysAgo, daysLeft),
		PackageVersion: pv,
		PublishDate:    publishDate,
		CooldownDays:   cooldownDays,
		DaysAgo:        daysAgo,
		DaysLeft:       daysLeft,
	})

	if global != nil {
		global.recordCooldownBlocked()
	}
}

// CooldownSkipReason is the only source that produces a dependency_cooldown_skipped
// event; trusted-package exemptions surface as install_trusted_allowed instead.
const CooldownSkipReason = "dependency_cooldown.skip"

// LogCooldownSkipped records that a specific package version was exempted from
// the dependency cooldown window by the dependency_cooldown.skip list.
func LogCooldownSkipped(pv *packagev1.PackageVersion) {
	logEvent(AuditEvent{
		Type:           EventTypeCooldownSkipped,
		Message:        fmt.Sprintf("Cooldown skipped for %s@%s", pkgName(pv), pkgVersion(pv)),
		PackageVersion: pv,
		Reason:         CooldownSkipReason,
		Details: map[string]any{
			"reason": CooldownSkipReason,
		},
	})
}

// LogSandboxOverride records that runtime sandbox policy overrides were applied.
func LogSandboxOverride(sandboxProfile string, overrides []map[string]string) {
	logEvent(AuditEvent{
		Type:    EventTypeSandboxOverride,
		Message: fmt.Sprintf("Sandbox runtime overrides applied (%d rules)", len(overrides)),
		Details: map[string]any{
			"sandbox_profile":           sandboxProfile,
			"sandbox_runtime_overrides": overrides,
		},
		ProfileName: sandboxProfile,
		Overrides:   overrides,
	})
}

// LogError records a significant error during PMG operation.
func LogError(message string, err error) {
	event := AuditEvent{
		Type:    EventTypeError,
		Message: message,
		Error:   err,
	}

	if err != nil {
		event.Details = map[string]any{
			"error": err.Error(),
		}
	}

	logEvent(event)
}

// LogSessionComplete records the end of a PMG invocation with aggregate session stats.
func LogSessionComplete(outcome Outcome, flowType FlowType) {
	if global == nil {
		return
	}

	s := global.getSession()
	if s == nil {
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	cfg := config.Get()

	LogSessionSummary(SessionData{
		PackageManager:       s.packageManager,
		FlowType:             flowType,
		Outcome:              outcome,
		TotalAnalyzed:        s.totalAnalyzed,
		AllowedCount:         s.allowedCount,
		BlockedCount:         s.blockedCount,
		ConfirmedCount:       s.confirmedCount,
		TrustedSkipped:       s.trustedSkipped,
		InsecureBypassed:     s.insecureBypassed,
		CooldownBlockedCount: s.cooldownBlockedCount,
		Duration:             time.Since(s.startTime),
		SandboxEnabled:       cfg.Config.Sandbox.Enabled,
		ParanoidMode:         cfg.Config.Paranoid,
		TransitiveEnabled:    cfg.Config.Transitive,
	})
}

// LogSessionSummary emits a session-complete audit event from explicit session
// data. The persistent proxy daemon uses this because it aggregates run stats in
// a stats collector rather than the per-invocation audit session that
// LogSessionComplete reads from.
func LogSessionSummary(data SessionData) {
	if global == nil {
		return
	}

	logEvent(AuditEvent{
		Type:        EventTypeSessionComplete,
		Message:     fmt.Sprintf("Session complete: %s", data.Outcome),
		SessionData: &data,
	})
}

func mergeDetails(base, extra map[string]any) map[string]any {
	if base == nil {
		base = make(map[string]any)
	}
	for k, v := range extra {
		base[k] = v
	}
	return base
}
