package audit

import (
	"context"
	"encoding/json"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"

	"github.com/kms/api-key-service/internal/config"
)

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

const (
	// Authentication events
	EventTypeLogin          EventType = "auth.login"
	EventTypeLoginFailed    EventType = "auth.login_failed"
	EventTypeLogout         EventType = "auth.logout"
	EventTypeTokenCreated   EventType = "auth.token_created"
	EventTypeTokenRevoked   EventType = "auth.token_revoked"
	EventTypeTokenValidated EventType = "auth.token_validated"
	
	// Session events
	EventTypeSessionCreated EventType = "session.created"
	EventTypeSessionRevoked EventType = "session.revoked"
	EventTypeSessionExpired EventType = "session.expired"
	
	// Application events
	EventTypeAppCreated EventType = "app.created"
	EventTypeAppUpdated EventType = "app.updated"
	EventTypeAppDeleted EventType = "app.deleted"
	
	// Permission events
	EventTypePermissionGranted EventType = "permission.granted"
	EventTypePermissionRevoked EventType = "permission.revoked"
	EventTypePermissionDenied  EventType = "permission.denied"
	
	// Tenant events
	EventTypeTenantCreated   EventType = "tenant.created"
	EventTypeTenantUpdated   EventType = "tenant.updated"
	EventTypeTenantSuspended EventType = "tenant.suspended"
	EventTypeTenantActivated EventType = "tenant.activated"
	
	// User events
	EventTypeUserCreated   EventType = "user.created"
	EventTypeUserUpdated   EventType = "user.updated"
	EventTypeUserSuspended EventType = "user.suspended"
	EventTypeUserActivated EventType = "user.activated"
	
	// Security events
	EventTypeSecurityViolation EventType = "security.violation"
	EventTypeBruteForceAttempt EventType = "security.brute_force"
	EventTypeIPBlocked         EventType = "security.ip_blocked"
	EventTypeRateLimitExceeded EventType = "security.rate_limit_exceeded"
	
	// System events
	EventTypeSystemStartup  EventType = "system.startup"
	EventTypeSystemShutdown EventType = "system.shutdown"
	EventTypeConfigChanged  EventType = "system.config_changed"
)

// EventSeverity represents the severity level of an audit event
type EventSeverity string

const (
	SeverityInfo     EventSeverity = "info"
	SeverityWarning  EventSeverity = "warning"
	SeverityError    EventSeverity = "error"
	SeverityCritical EventSeverity = "critical"
)

// EventStatus represents the status of an audit event
type EventStatus string

const (
	StatusSuccess EventStatus = "success"
	StatusFailure EventStatus = "failure"
	StatusPending EventStatus = "pending"
)

// AuditEvent represents a single audit event
type AuditEvent struct {
	ID          uuid.UUID              `json:"id" db:"id"`
	Type        EventType              `json:"type" db:"type"`
	Severity    EventSeverity          `json:"severity" db:"severity"`
	Status      EventStatus            `json:"status" db:"status"`
	Timestamp   time.Time              `json:"timestamp" db:"timestamp"`
	
	// Actor information
	ActorID     string                 `json:"actor_id,omitempty" db:"actor_id"`
	ActorType   string                 `json:"actor_type,omitempty" db:"actor_type"` // user, system, service
	ActorIP     string                 `json:"actor_ip,omitempty" db:"actor_ip"`
	UserAgent   string                 `json:"user_agent,omitempty" db:"user_agent"`
	
	// Tenant information
	TenantID    *uuid.UUID             `json:"tenant_id,omitempty" db:"tenant_id"`
	
	// Resource information
	ResourceID   string                `json:"resource_id,omitempty" db:"resource_id"`
	ResourceType string                `json:"resource_type,omitempty" db:"resource_type"`
	
	// Event details
	Action      string                 `json:"action" db:"action"`
	Description string                 `json:"description" db:"description"`
	Details     map[string]interface{} `json:"details,omitempty" db:"details"`
	
	// Request context
	RequestID   string                 `json:"request_id,omitempty" db:"request_id"`
	SessionID   string                 `json:"session_id,omitempty" db:"session_id"`
	
	// Additional metadata
	Tags        []string               `json:"tags,omitempty" db:"tags"`
	Metadata    map[string]string      `json:"metadata,omitempty" db:"metadata"`
}

// AuditLogger defines the interface for audit logging
type AuditLogger interface {
	// LogEvent logs a single audit event
	LogEvent(ctx context.Context, event *AuditEvent) error
	
	// LogAuthEvent logs an authentication-related event
	LogAuthEvent(ctx context.Context, eventType EventType, actorID, actorIP string, details map[string]interface{}) error
	
	// LogPermissionEvent logs a permission-related event
	LogPermissionEvent(ctx context.Context, eventType EventType, actorID, resourceID, resourceType string, details map[string]interface{}) error
	
	// LogSecurityEvent logs a security-related event
	LogSecurityEvent(ctx context.Context, eventType EventType, actorIP string, severity EventSeverity, details map[string]interface{}) error
	
	// LogSystemEvent logs a system-related event
	LogSystemEvent(ctx context.Context, eventType EventType, details map[string]interface{}) error
	
	// QueryEvents queries audit events with filters
	QueryEvents(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error)
	
	// GetEventStats returns audit event statistics
	GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error)
}

// AuditFilter represents filters for querying audit events
type AuditFilter struct {
	EventTypes   []EventType    `json:"event_types,omitempty"`
	Severities   []EventSeverity `json:"severities,omitempty"`
	Statuses     []EventStatus  `json:"statuses,omitempty"`
	ActorID      string         `json:"actor_id,omitempty"`
	ActorType    string         `json:"actor_type,omitempty"`
	TenantID     *uuid.UUID     `json:"tenant_id,omitempty"`
	ResourceID   string         `json:"resource_id,omitempty"`
	ResourceType string         `json:"resource_type,omitempty"`
	StartTime    *time.Time     `json:"start_time,omitempty"`
	EndTime      *time.Time     `json:"end_time,omitempty"`
	Tags         []string       `json:"tags,omitempty"`
	Limit        int            `json:"limit"`
	Offset       int            `json:"offset"`
	OrderBy      string         `json:"order_by"` // timestamp, type, severity
	OrderDesc    bool           `json:"order_desc"`
}

// AuditStatsFilter represents filters for audit statistics
type AuditStatsFilter struct {
	EventTypes []EventType `json:"event_types,omitempty"`
	TenantID   *uuid.UUID  `json:"tenant_id,omitempty"`
	StartTime  *time.Time  `json:"start_time,omitempty"`
	EndTime    *time.Time  `json:"end_time,omitempty"`
	GroupBy    string      `json:"group_by"` // type, severity, status, hour, day
}

// AuditStats represents audit event statistics
type AuditStats struct {
	TotalEvents int                    `json:"total_events"`
	ByType      map[EventType]int      `json:"by_type"`
	BySeverity  map[EventSeverity]int  `json:"by_severity"`
	ByStatus    map[EventStatus]int    `json:"by_status"`
	ByTime      map[string]int         `json:"by_time,omitempty"`
}

// auditLogger implements the AuditLogger interface
type auditLogger struct {
	config     config.ConfigProvider
	logger     *zap.Logger
	repository AuditRepository
}

// AuditRepository defines the interface for audit event storage
type AuditRepository interface {
	Create(ctx context.Context, event *AuditEvent) error
	Query(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error)
	GetStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error)
	DeleteOldEvents(ctx context.Context, olderThan time.Time) (int, error)
}

// NewAuditLogger creates a new audit logger
func NewAuditLogger(config config.ConfigProvider, logger *zap.Logger, repository AuditRepository) AuditLogger {
	return &auditLogger{
		config:     config,
		logger:     logger,
		repository: repository,
	}
}

// LogEvent logs a single audit event
func (a *auditLogger) LogEvent(ctx context.Context, event *AuditEvent) error {
	// Set default values
	if event.ID == uuid.Nil {
		event.ID = uuid.New()
	}
	if event.Timestamp.IsZero() {
		event.Timestamp = time.Now().UTC()
	}
	if event.Severity == "" {
		event.Severity = SeverityInfo
	}
	if event.Status == "" {
		event.Status = StatusSuccess
	}

	// Extract request context if available
	if requestID := ctx.Value("request_id"); requestID != nil {
		if reqID, ok := requestID.(string); ok {
			event.RequestID = reqID
		}
	}

	// Log to structured logger
	a.logToStructuredLogger(event)

	// Store in repository
	if err := a.repository.Create(ctx, event); err != nil {
		a.logger.Error("Failed to store audit event", 
			zap.Error(err),
			zap.String("event_id", event.ID.String()),
			zap.String("event_type", string(event.Type)))
		return err
	}

	return nil
}

// LogAuthEvent logs an authentication-related event
func (a *auditLogger) LogAuthEvent(ctx context.Context, eventType EventType, actorID, actorIP string, details map[string]interface{}) error {
	severity := SeverityInfo
	status := StatusSuccess
	
	// Determine severity and status based on event type
	switch eventType {
	case EventTypeLoginFailed:
		severity = SeverityWarning
		status = StatusFailure
	case EventTypeTokenRevoked:
		severity = SeverityWarning
	}

	event := &AuditEvent{
		Type:        eventType,
		Severity:    severity,
		Status:      status,
		ActorID:     actorID,
		ActorType:   "user",
		ActorIP:     actorIP,
		Action:      string(eventType),
		Description: a.generateDescription(eventType, details),
		Details:     details,
		Tags:        []string{"authentication"},
	}

	return a.LogEvent(ctx, event)
}

// LogPermissionEvent logs a permission-related event
func (a *auditLogger) LogPermissionEvent(ctx context.Context, eventType EventType, actorID, resourceID, resourceType string, details map[string]interface{}) error {
	severity := SeverityInfo
	status := StatusSuccess
	
	// Determine severity and status based on event type
	switch eventType {
	case EventTypePermissionDenied:
		severity = SeverityWarning
		status = StatusFailure
	}

	event := &AuditEvent{
		Type:         eventType,
		Severity:     severity,
		Status:       status,
		ActorID:      actorID,
		ActorType:    "user",
		ResourceID:   resourceID,
		ResourceType: resourceType,
		Action:       string(eventType),
		Description:  a.generateDescription(eventType, details),
		Details:      details,
		Tags:         []string{"permission", "authorization"},
	}

	return a.LogEvent(ctx, event)
}

// LogSecurityEvent logs a security-related event
func (a *auditLogger) LogSecurityEvent(ctx context.Context, eventType EventType, actorIP string, severity EventSeverity, details map[string]interface{}) error {
	status := StatusSuccess
	if severity == SeverityError || severity == SeverityCritical {
		status = StatusFailure
	}

	event := &AuditEvent{
		Type:        eventType,
		Severity:    severity,
		Status:      status,
		ActorIP:     actorIP,
		ActorType:   "system",
		Action:      string(eventType),
		Description: a.generateDescription(eventType, details),
		Details:     details,
		Tags:        []string{"security"},
	}

	return a.LogEvent(ctx, event)
}

// LogSystemEvent logs a system-related event
func (a *auditLogger) LogSystemEvent(ctx context.Context, eventType EventType, details map[string]interface{}) error {
	event := &AuditEvent{
		Type:        eventType,
		Severity:    SeverityInfo,
		Status:      StatusSuccess,
		ActorType:   "system",
		Action:      string(eventType),
		Description: a.generateDescription(eventType, details),
		Details:     details,
		Tags:        []string{"system"},
	}

	return a.LogEvent(ctx, event)
}

// QueryEvents queries audit events with filters
func (a *auditLogger) QueryEvents(ctx context.Context, filter *AuditFilter) ([]*AuditEvent, error) {
	// Set default pagination
	if filter.Limit <= 0 {
		filter.Limit = 100
	}
	if filter.Limit > 1000 {
		filter.Limit = 1000
	}
	if filter.OrderBy == "" {
		filter.OrderBy = "timestamp"
		filter.OrderDesc = true
	}

	return a.repository.Query(ctx, filter)
}

// GetEventStats returns audit event statistics
func (a *auditLogger) GetEventStats(ctx context.Context, filter *AuditStatsFilter) (*AuditStats, error) {
	return a.repository.GetStats(ctx, filter)
}

// logToStructuredLogger logs the event to the structured logger
func (a *auditLogger) logToStructuredLogger(event *AuditEvent) {
	fields := []zap.Field{
		zap.String("audit_event_id", event.ID.String()),
		zap.String("event_type", string(event.Type)),
		zap.String("severity", string(event.Severity)),
		zap.String("status", string(event.Status)),
		zap.Time("timestamp", event.Timestamp),
		zap.String("action", event.Action),
		zap.String("description", event.Description),
	}

	if event.ActorID != "" {
		fields = append(fields, zap.String("actor_id", event.ActorID))
	}
	if event.ActorType != "" {
		fields = append(fields, zap.String("actor_type", event.ActorType))
	}
	if event.ActorIP != "" {
		fields = append(fields, zap.String("actor_ip", event.ActorIP))
	}
	if event.TenantID != nil {
		fields = append(fields, zap.String("tenant_id", event.TenantID.String()))
	}
	if event.ResourceID != "" {
		fields = append(fields, zap.String("resource_id", event.ResourceID))
	}
	if event.ResourceType != "" {
		fields = append(fields, zap.String("resource_type", event.ResourceType))
	}
	if event.RequestID != "" {
		fields = append(fields, zap.String("request_id", event.RequestID))
	}
	if event.SessionID != "" {
		fields = append(fields, zap.String("session_id", event.SessionID))
	}
	if len(event.Tags) > 0 {
		fields = append(fields, zap.Strings("tags", event.Tags))
	}
	if event.Details != nil {
		if detailsJSON, err := json.Marshal(event.Details); err == nil {
			fields = append(fields, zap.String("details", string(detailsJSON)))
		}
	}

	// Log at appropriate level based on severity
	switch event.Severity {
	case SeverityInfo:
		a.logger.Info("Audit event", fields...)
	case SeverityWarning:
		a.logger.Warn("Audit event", fields...)
	case SeverityError:
		a.logger.Error("Audit event", fields...)
	case SeverityCritical:
		a.logger.Error("Critical audit event", fields...)
	default:
		a.logger.Info("Audit event", fields...)
	}
}

// generateDescription generates a human-readable description for an event
func (a *auditLogger) generateDescription(eventType EventType, details map[string]interface{}) string {
	switch eventType {
	case EventTypeLogin:
		return "User successfully logged in"
	case EventTypeLoginFailed:
		return "User login attempt failed"
	case EventTypeLogout:
		return "User logged out"
	case EventTypeTokenCreated:
		return "API token created"
	case EventTypeTokenRevoked:
		return "API token revoked"
	case EventTypeTokenValidated:
		return "API token validated"
	case EventTypeSessionCreated:
		return "User session created"
	case EventTypeSessionRevoked:
		return "User session revoked"
	case EventTypeSessionExpired:
		return "User session expired"
	case EventTypeAppCreated:
		return "Application created"
	case EventTypeAppUpdated:
		return "Application updated"
	case EventTypeAppDeleted:
		return "Application deleted"
	case EventTypePermissionGranted:
		return "Permission granted"
	case EventTypePermissionRevoked:
		return "Permission revoked"
	case EventTypePermissionDenied:
		return "Permission denied"
	case EventTypeTenantCreated:
		return "Tenant created"
	case EventTypeTenantUpdated:
		return "Tenant updated"
	case EventTypeTenantSuspended:
		return "Tenant suspended"
	case EventTypeTenantActivated:
		return "Tenant activated"
	case EventTypeUserCreated:
		return "User created"
	case EventTypeUserUpdated:
		return "User updated"
	case EventTypeUserSuspended:
		return "User suspended"
	case EventTypeUserActivated:
		return "User activated"
	case EventTypeSecurityViolation:
		return "Security violation detected"
	case EventTypeBruteForceAttempt:
		return "Brute force attempt detected"
	case EventTypeIPBlocked:
		return "IP address blocked"
	case EventTypeRateLimitExceeded:
		return "Rate limit exceeded"
	case EventTypeSystemStartup:
		return "System started"
	case EventTypeSystemShutdown:
		return "System shutdown"
	case EventTypeConfigChanged:
		return "Configuration changed"
	default:
		return string(eventType)
	}
}

// AuditEventBuilder provides a fluent interface for building audit events
type AuditEventBuilder struct {
	event *AuditEvent
}

// NewAuditEventBuilder creates a new audit event builder
func NewAuditEventBuilder(eventType EventType) *AuditEventBuilder {
	return &AuditEventBuilder{
		event: &AuditEvent{
			ID:        uuid.New(),
			Type:      eventType,
			Timestamp: time.Now().UTC(),
			Severity:  SeverityInfo,
			Status:    StatusSuccess,
			Details:   make(map[string]interface{}),
			Metadata:  make(map[string]string),
		},
	}
}

// WithSeverity sets the event severity
func (b *AuditEventBuilder) WithSeverity(severity EventSeverity) *AuditEventBuilder {
	b.event.Severity = severity
	return b
}

// WithStatus sets the event status
func (b *AuditEventBuilder) WithStatus(status EventStatus) *AuditEventBuilder {
	b.event.Status = status
	return b
}

// WithActor sets the actor information
func (b *AuditEventBuilder) WithActor(actorID, actorType, actorIP string) *AuditEventBuilder {
	b.event.ActorID = actorID
	b.event.ActorType = actorType
	b.event.ActorIP = actorIP
	return b
}

// WithTenant sets the tenant ID
func (b *AuditEventBuilder) WithTenant(tenantID uuid.UUID) *AuditEventBuilder {
	b.event.TenantID = &tenantID
	return b
}

// WithResource sets the resource information
func (b *AuditEventBuilder) WithResource(resourceID, resourceType string) *AuditEventBuilder {
	b.event.ResourceID = resourceID
	b.event.ResourceType = resourceType
	return b
}

// WithAction sets the action
func (b *AuditEventBuilder) WithAction(action string) *AuditEventBuilder {
	b.event.Action = action
	return b
}

// WithDescription sets the description
func (b *AuditEventBuilder) WithDescription(description string) *AuditEventBuilder {
	b.event.Description = description
	return b
}

// WithDetail adds a detail
func (b *AuditEventBuilder) WithDetail(key string, value interface{}) *AuditEventBuilder {
	b.event.Details[key] = value
	return b
}

// WithDetails sets multiple details
func (b *AuditEventBuilder) WithDetails(details map[string]interface{}) *AuditEventBuilder {
	for k, v := range details {
		b.event.Details[k] = v
	}
	return b
}

// WithRequestContext sets request context information
func (b *AuditEventBuilder) WithRequestContext(requestID, sessionID string) *AuditEventBuilder {
	b.event.RequestID = requestID
	b.event.SessionID = sessionID
	return b
}

// WithTags sets the tags
func (b *AuditEventBuilder) WithTags(tags ...string) *AuditEventBuilder {
	b.event.Tags = tags
	return b
}

// WithMetadata adds metadata
func (b *AuditEventBuilder) WithMetadata(key, value string) *AuditEventBuilder {
	b.event.Metadata[key] = value
	return b
}

// Build returns the built audit event
func (b *AuditEventBuilder) Build() *AuditEvent {
	return b.event
}
