using System.Text.Json;
using Dapper;
using KArtSell.BuildingBlocks.Data;
using KArtSell.BuildingBlocks.Reliability;
using Microsoft.Extensions.Logging;

namespace KArtSell.Host.Consumers;

/// <summary>
/// DEBT-029: Audit Trail Consumer - Maps outbox events to compliance audit trail.
/// Implements event-driven integration for operation_audit_trail enrichment.
///
/// Design: Outbox events are consumed and logged to compliance.operation_audit_trail
/// for full traceability across all slices (ApprovalWorkflow, TradeExecution, etc.).
/// Idempotent: Same event logged once (replayed events are no-ops via idempotency).
/// </summary>
public sealed class AuditTrailConsumer : IOutboxEventConsumer
{
    private readonly IDbConnectionFactory _connectionFactory;
    private readonly ILogger<AuditTrailConsumer> _logger;

    private static readonly Action<ILogger, string, Guid, string, Exception?> LogEventConsumed =
        LoggerMessage.Define<string, Guid, string>(
            LogLevel.Information,
            new EventId(1, nameof(LogEventConsumed)),
            "Audit trail: {EventType} for entity {EntityId} (correlation: {CorrelationId})");

    private static readonly Action<ILogger, string, Exception?> LogEventIgnored =
        LoggerMessage.Define<string>(
            LogLevel.Debug,
            new EventId(2, nameof(LogEventIgnored)),
            "Outbox event {EventType} does not map to audit trail (non-auditable)");

    public AuditTrailConsumer(IDbConnectionFactory connectionFactory, ILogger<AuditTrailConsumer> logger)
    {
        _connectionFactory = connectionFactory;
        _logger = logger;
    }

    /// <summary>
    /// Route outbox events to compliance audit trail.
    /// Events not in the map are silently ignored (non-auditable operations).
    /// </summary>
    public async Task ConsumeAsync(DapperOutboxMessageReader.OutboxMessageRow message, CancellationToken ct)
    {
        // Parse correlation ID from payload or use message ID
        var auditEntry = MapEventToAuditEntry(message);
        if (auditEntry == null)
        {
            LogEventIgnored(_logger, message.EventType, null);
            return;
        }

        const string sql = """
            INSERT INTO compliance.operation_audit_trail
            (event_type, correlation_id, entity_type, entity_id, details, detected_at, published_at)
            VALUES (@EventType, @CorrelationId, @EntityType, @EntityId, @Details::jsonb, @DetectedAt, NOW())
            ON CONFLICT DO NOTHING
            """;

        try
        {
            await using var connection = await _connectionFactory.OpenAsync(ct);
            await connection.ExecuteAsync(
                sql,
                new
                {
                    auditEntry.EventType,
                    auditEntry.CorrelationId,
                    auditEntry.EntityType,
                    auditEntry.EntityId,
                    Details = auditEntry.Details != null ? JsonSerializer.Serialize(auditEntry.Details) : null,
                    DetectedAt = message.OccurredAt
                });

            LogEventConsumed(_logger, message.EventType, auditEntry.EntityId, message.CorrelationId, null);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to log audit trail for {EventType} {MessageId}", message.EventType, message.MessageId);
            throw;
        }
    }

    private AuditEntryMap? MapEventToAuditEntry(DapperOutboxMessageReader.OutboxMessageRow message)
    {
        return message.EventType switch
        {
            // Approval Workflow Events
            "APPROVAL_PROPOSED" => ParseAuditEntry(message, "APPROVAL_PROPOSED", "approval_proposal"),
            "APPROVAL_APPROVED" => ParseAuditEntry(message, "APPROVAL_APPROVED", "approval_proposal"),
            "APPROVAL_REJECTED" => ParseAuditEntry(message, "APPROVAL_REJECTED", "approval_proposal"),

            // Model Operations Events
            "MODEL_ACTIVATED" => ParseAuditEntry(message, "MODEL_ACTIVATED", "model"),
            "MODEL_DEACTIVATED" => ParseAuditEntry(message, "MODEL_DEACTIVATED", "model"),
            "SHADOW_RUN_COMPLETED" => ParseAuditEntry(message, "SHADOW_RUN_COMPLETED", "shadow_run"),

            // Trade Execution Events
            "TRADE_SUBMITTED" => ParseAuditEntry(message, "TRADE_SUBMITTED", "trade"),
            "TRADE_CONFIRMED" => ParseAuditEntry(message, "TRADE_CONFIRMED", "trade"),
            "TRADE_FAILED" => ParseAuditEntry(message, "TRADE_FAILED", "trade"),

            // Sell Decision Events
            "SELL_DECISION_MADE" => ParseAuditEntry(message, "SELL_DECISION_MADE", "sell_decision"),
            "SELL_EXECUTED" => ParseAuditEntry(message, "SELL_EXECUTED", "sell_decision"),

            // Portfolio Reconciliation Events
            "RECONCILIATION_STARTED" => ParseAuditEntry(message, "RECONCILIATION_STARTED", "reconciliation"),
            "RECONCILIATION_COMPLETED" => ParseAuditEntry(message, "RECONCILIATION_COMPLETED", "reconciliation"),

            // Non-auditable events (informational only)
            _ => null
        };
    }

    private AuditEntryMap? ParseAuditEntry(
        DapperOutboxMessageReader.OutboxMessageRow message,
        string auditEventType,
        string entityType)
    {
        try
        {
            var payload = JsonDocument.Parse(message.PayloadJson);
            var root = payload.RootElement;

            // Extract entity ID from common payload locations
            var entityId = root.TryGetProperty("id", out var idProp) ? Guid.Parse(idProp.GetString()!) :
                           root.TryGetProperty("entityId", out var entIdProp) ? Guid.Parse(entIdProp.GetString()!) :
                           root.TryGetProperty("proposalId", out var propIdProp) ? Guid.Parse(propIdProp.GetString()!) :
                           Guid.Empty;

            if (entityId == Guid.Empty)
            {
                _logger.LogWarning("Could not extract entity ID from {EventType} payload", message.EventType);
                return null;
            }

            return new AuditEntryMap
            {
                EventType = auditEventType,
                EntityType = entityType,
                EntityId = entityId,
                CorrelationId = Guid.TryParse(message.CorrelationId, out var correlationId) ? correlationId : Guid.Empty,
                Details = new Dictionary<string, object>
                {
                    { "originatingEventType", message.EventType },
                    { "payloadHash", message.PayloadHash },
                    { "schemaVersion", message.SchemaVersion }
                }
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to parse audit entry for {EventType}", message.EventType);
            return null;
        }
    }

    private sealed class AuditEntryMap
    {
        public string EventType { get; set; } = string.Empty;
        public string EntityType { get; set; } = string.Empty;
        public Guid EntityId { get; set; }
        public Guid CorrelationId { get; set; }
        public Dictionary<string, object>? Details { get; set; }
    }
}

/// <summary>
/// Generic outbox event consumer interface (extends IInboxConsumer for compatibility).
/// </summary>
public interface IOutboxEventConsumer
{
    Task ConsumeAsync(DapperOutboxMessageReader.OutboxMessageRow message, CancellationToken ct);
}
