// Package auditlog provides security audit logging for compliance and security monitoring.
// Audit logs are separate from application logs and capture security-relevant events
// with consistent structure for analysis and alerting.
package auditlog

import (
	"context"
	"time"

	"github.com/gocql/gocql"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)

// EventType represents the type of security event
type EventType string

const (
	// Authentication events
	EventTypeLoginAttempt    EventType = "login_attempt"
	EventTypeLoginSuccess    EventType = "login_success"
	EventTypeLoginFailure    EventType = "login_failure"
	EventTypeLogout          EventType = "logout"
	EventTypeTokenRefresh    EventType = "token_refresh"
	EventTypeTokenRevoked    EventType = "token_revoked"

	// Account events
	EventTypeAccountCreated  EventType = "account_created"
	EventTypeAccountDeleted  EventType = "account_deleted"
	EventTypeAccountLocked   EventType = "account_locked"
	EventTypeAccountUnlocked EventType = "account_unlocked"
	EventTypeEmailVerified   EventType = "email_verified"

	// Recovery events
	EventTypeRecoveryInitiated EventType = "recovery_initiated"
	EventTypeRecoveryCompleted EventType = "recovery_completed"
	EventTypeRecoveryFailed    EventType = "recovery_failed"

	// Access control events
	EventTypeAccessDenied      EventType = "access_denied"
	EventTypePermissionChanged EventType = "permission_changed"

	// Sharing events
	EventTypeCollectionShared   EventType = "collection_shared"
	EventTypeCollectionUnshared EventType = "collection_unshared"
	EventTypeSharingBlocked     EventType = "sharing_blocked"
)

// Outcome represents the result of the audited action
type Outcome string

const (
	OutcomeSuccess Outcome = "success"
	OutcomeFailure Outcome = "failure"
	OutcomeBlocked Outcome = "blocked"
)

// AuditEvent represents a security audit event
type AuditEvent struct {
	Timestamp   time.Time         `json:"timestamp"`
	EventType   EventType         `json:"event_type"`
	Outcome     Outcome           `json:"outcome"`
	UserID      string            `json:"user_id,omitempty"`
	Email       string            `json:"email,omitempty"` // Always masked
	ClientIP    string            `json:"client_ip,omitempty"`
	UserAgent   string            `json:"user_agent,omitempty"`
	Resource    string            `json:"resource,omitempty"`
	Action      string            `json:"action,omitempty"`
	Details     map[string]string `json:"details,omitempty"`
	FailReason  string            `json:"fail_reason,omitempty"`
}

// AuditLogger provides security audit logging functionality
type AuditLogger interface {
	// Log records a security audit event
	Log(ctx context.Context, event AuditEvent)

	// LogAuth logs an authentication event with common fields
	LogAuth(ctx context.Context, eventType EventType, outcome Outcome, email string, clientIP string, details map[string]string)

	// LogAccess logs an access control event
	LogAccess(ctx context.Context, eventType EventType, outcome Outcome, userID string, resource string, action string, details map[string]string)
}

type auditLoggerImpl struct {
	logger *zap.Logger
}

// NewAuditLogger creates a new audit logger
func NewAuditLogger(logger *zap.Logger) AuditLogger {
	// Create a named logger specifically for audit events
	// This allows filtering audit logs separately from application logs
	auditLogger := logger.Named("AUDIT")

	return &auditLoggerImpl{
		logger: auditLogger,
	}
}

// Log records a security audit event
func (a *auditLoggerImpl) Log(ctx context.Context, event AuditEvent) {
	// Set timestamp if not provided
	if event.Timestamp.IsZero() {
		event.Timestamp = time.Now().UTC()
	}

	// Build zap fields
	fields := []zap.Field{
		zap.String("audit_event", string(event.EventType)),
		zap.String("outcome", string(event.Outcome)),
		zap.Time("event_time", event.Timestamp),
	}

	if event.UserID != "" {
		fields = append(fields, zap.String("user_id", event.UserID))
	}
	if event.Email != "" {
		fields = append(fields, zap.String("email", validation.MaskEmail(event.Email))) // Always mask for safety
	}
	if event.ClientIP != "" {
		fields = append(fields, zap.String("client_ip", validation.MaskIP(event.ClientIP))) // Always mask for safety
	}
	if event.UserAgent != "" {
		fields = append(fields, zap.String("user_agent", event.UserAgent))
	}
	if event.Resource != "" {
		fields = append(fields, zap.String("resource", event.Resource))
	}
	if event.Action != "" {
		fields = append(fields, zap.String("action", event.Action))
	}
	if event.FailReason != "" {
		fields = append(fields, zap.String("fail_reason", event.FailReason))
	}
	if len(event.Details) > 0 {
		fields = append(fields, zap.Any("details", event.Details))
	}

	// Try to get request ID from context
	if requestID, ok := ctx.Value(constants.SessionID).(string); ok && requestID != "" {
		fields = append(fields, zap.String("request_id", requestID))
	}

	// Log at INFO level - audit events are always important
	a.logger.Info("security_audit", fields...)
}

// LogAuth logs an authentication event with common fields
func (a *auditLoggerImpl) LogAuth(ctx context.Context, eventType EventType, outcome Outcome, email string, clientIP string, details map[string]string) {
	event := AuditEvent{
		Timestamp: time.Now().UTC(),
		EventType: eventType,
		Outcome:   outcome,
		Email:     email, // Should be pre-masked by caller
		ClientIP:  clientIP,
		Details:   details,
	}

	// Extract user ID from context if available
	if userID, ok := ctx.Value(constants.SessionUserID).(gocql.UUID); ok {
		event.UserID = userID.String()
	}

	a.Log(ctx, event)
}

// LogAccess logs an access control event
func (a *auditLoggerImpl) LogAccess(ctx context.Context, eventType EventType, outcome Outcome, userID string, resource string, action string, details map[string]string) {
	event := AuditEvent{
		Timestamp: time.Now().UTC(),
		EventType: eventType,
		Outcome:   outcome,
		UserID:    userID,
		Resource:  resource,
		Action:    action,
		Details:   details,
	}

	a.Log(ctx, event)
}
