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

namespace KArtSell.Modules.ModelOperations.Compliance;

public class AuditSql
{
    private readonly ILogger<AuditSql> _logger;

    public AuditSql(ILogger<AuditSql> logger)
    {
        _logger = logger;
    }

    /// <summary>
    /// Insert audit event (immutable, append-only).
    /// </summary>
    public async Task InsertAuditEventAsync(
        IDbConnection db,
        Guid id,
        string eventType,
        string entityType,
        Guid entityId,
        string actorEmail,
        string? actorRole,
        DateTime eventAt,
        string result,
        string? errorMessage,
        Dictionary<string, object>? details,
        string[]? evidenceLinks,
        string? ipAddress,
        string? userAgent,
        Guid correlationId,
        CancellationToken ct)
    {
        const string sql = """
            INSERT INTO compliance.audit_events
            (id, event_type, entity_type, entity_id, actor_email, actor_role, event_at, result,
             error_message, details, evidence_links, ip_address, user_agent, published_at,
             correlation_id, revision)
            VALUES (@Id, @EventType, @EntityType, @EntityId, @ActorEmail, @ActorRole, @EventAt,
                    @Result, @ErrorMessage, @Details::jsonb, @EvidenceLinks, @IpAddress::inet, @UserAgent,
                    NOW(), @CorrelationId, 1)
        """;

        await db.ExecuteAsync(
            sql,
            new
            {
                Id = id,
                EventType = eventType,
                EntityType = entityType,
                EntityId = entityId,
                ActorEmail = actorEmail,
                ActorRole = actorRole,
                EventAt = eventAt,
                Result = result,
                ErrorMessage = errorMessage,
                Details = details == null ? null : JsonSerializer.Serialize(details),
                EvidenceLinks = evidenceLinks,
                IpAddress = ipAddress,
                UserAgent = userAgent,
                CorrelationId = correlationId
            });

        _logger.LogInformation(
            "Audit event logged: {EventType} for {EntityType} {EntityId} by {ActorEmail}",
            eventType, entityType, entityId, actorEmail);
    }

    /// <summary>
    /// Query audit events with filters (compliance officer query).
    /// </summary>
    public async Task<(List<AuditEvent> Events, int Total)> QueryAuditEventsAsync(
        IDbConnection db,
        Guid? entityId = null,
        string? eventType = null,
        DateTime? dateFrom = null,
        DateTime? dateTo = null,
        string? actorEmail = null,
        int skip = 0,
        int take = 50,
        CancellationToken ct = default)
    {
        var whereClauses = new List<string>
        {
            "1=1"  // Always true, allows clean AND logic
        };
        var parameters = new DynamicParameters();

        if (entityId.HasValue)
        {
            whereClauses.Add("entity_id = @EntityId");
            parameters.Add("@EntityId", entityId.Value);
        }

        if (!string.IsNullOrEmpty(eventType))
        {
            whereClauses.Add("event_type = @EventType");
            parameters.Add("@EventType", eventType);
        }

        if (dateFrom.HasValue)
        {
            whereClauses.Add("event_at >= @DateFrom");
            parameters.Add("@DateFrom", dateFrom.Value);
        }

        if (dateTo.HasValue)
        {
            whereClauses.Add("event_at <= @DateTo");
            parameters.Add("@DateTo", dateTo.Value);
        }

        if (!string.IsNullOrEmpty(actorEmail))
        {
            whereClauses.Add("actor_email ILIKE @ActorEmail");
            parameters.Add("@ActorEmail", $"%{actorEmail}%");
        }

        var whereClause = string.Join(" AND ", whereClauses);

        // Get total count
        var countSql = $"""
            SELECT COUNT(*)
            FROM compliance.audit_events
            WHERE {whereClause}
        """;
        var total = await db.QuerySingleAsync<int>(countSql, parameters);

        // Get paginated results
        var sql = $"""
            SELECT id, event_type, entity_type, entity_id, actor_email, actor_role, event_at,
                   result, error_message, details, evidence_links, ip_address::text as ip_address, user_agent,
                   published_at, correlation_id, revision
            FROM compliance.audit_events
            WHERE {whereClause}
            ORDER BY event_at DESC
            OFFSET @Skip ROWS
            FETCH NEXT @Take ROWS ONLY
        """;
        parameters.Add("@Skip", skip);
        parameters.Add("@Take", take);

        var raw = await db.QueryAsync<AuditEventRaw>(sql, parameters);
        var events = raw.Select(ToAuditEvent).ToList();

        return (events, total);
    }

    /// <summary>
    /// Get single audit event by ID.
    /// </summary>
    public async Task<AuditEvent?> GetAuditEventByIdAsync(
        IDbConnection db,
        Guid eventId,
        CancellationToken ct = default)
    {
        const string sql = """
            SELECT id, event_type, entity_type, entity_id, actor_email, actor_role, event_at,
                   result, error_message, details, evidence_links, ip_address::text as ip_address, user_agent,
                   published_at, correlation_id, revision
            FROM compliance.audit_events
            WHERE id = @EventId
        """;

        var raw = await db.QuerySingleOrDefaultAsync<AuditEventRaw>(sql, new { EventId = eventId });
        return raw == null ? null : ToAuditEvent(raw);
    }

    private static AuditEvent ToAuditEvent(AuditEventRaw raw) => new()
    {
        Id = raw.Id,
        EventType = raw.EventType,
        EntityType = raw.EntityType,
        EntityId = raw.EntityId,
        ActorEmail = raw.ActorEmail,
        ActorRole = raw.ActorRole,
        EventAt = raw.EventAt,
        Result = raw.Result,
        ErrorMessage = raw.ErrorMessage,
        Details = raw.Details == null ? null : JsonSerializer.Deserialize<Dictionary<string, object>>(raw.Details),
        EvidenceLinks = raw.EvidenceLinks,
        IpAddress = raw.IpAddress,
        UserAgent = raw.UserAgent,
        PublishedAt = raw.PublishedAt,
        CorrelationId = raw.CorrelationId,
        Revision = raw.Revision
    };

    private sealed class AuditEventRaw
    {
        public Guid Id { get; set; }
        public required string EventType { get; set; }
        public required string EntityType { get; set; }
        public Guid EntityId { get; set; }
        public required string ActorEmail { get; set; }
        public string? ActorRole { get; set; }
        public DateTime EventAt { get; set; }
        public required string Result { get; set; }
        public string? ErrorMessage { get; set; }
        public string? Details { get; set; }
        public string[]? EvidenceLinks { get; set; }
        public string? IpAddress { get; set; }
        public string? UserAgent { get; set; }
        public DateTime PublishedAt { get; set; }
        public Guid CorrelationId { get; set; }
        public int Revision { get; set; }
    }

    /// <summary>
    /// Insert GDPR retention tracking record.
    /// </summary>
    public async Task InsertGdprRetentionAsync(
        IDbConnection db,
        Guid id,
        Guid eventId,
        Guid? customerId,
        string[]? dataCategories,
        DateTime retentionEndsAt,
        CancellationToken ct = default)
    {
        const string sql = """
            INSERT INTO compliance.gdpr_retention
            (id, event_id, customer_id, data_categories, retention_ends_at, purge_status, published_at, revision)
            VALUES (@Id, @EventId, @CustomerId, @DataCategories, @RetentionEndsAt, 'PENDING', NOW(), 1)
        """;

        await db.ExecuteAsync(
            sql,
            new
            {
                Id = id,
                EventId = eventId,
                CustomerId = customerId,
                DataCategories = dataCategories,
                RetentionEndsAt = retentionEndsAt
            });
    }

    /// <summary>
    /// Mark GDPR retention as PURGED (right-to-be-forgotten).
    /// </summary>
    public async Task MarkGdprPurgedAsync(
        IDbConnection db,
        Guid customerId,
        CancellationToken ct = default)
    {
        const string sql = """
            UPDATE compliance.gdpr_retention
            SET purge_status = @PurgeStatus, purged_at = NOW(), revision = revision + 1
            WHERE customer_id = @CustomerId AND purge_status = 'PENDING'
        """;

        var affected = await db.ExecuteAsync(sql, new
        {
            CustomerId = customerId,
            PurgeStatus = GdprPurgeStatus.Purged
        });

        _logger.LogInformation(
            "GDPR purge marked for {CustomerId}: {AffectedRecords} records",
            customerId, affected);
    }

    /// <summary>
    /// Get pending GDPR retention records for purging.
    /// </summary>
    public async Task<List<GdprRetention>> GetPendingGdprRetentionsAsync(
        IDbConnection db,
        CancellationToken ct = default)
    {
        const string sql = """
            SELECT id, event_id, customer_id, data_categories, retention_ends_at,
                   purge_status, purged_at, exception_reason, published_at, revision
            FROM compliance.gdpr_retention
            WHERE purge_status = 'PENDING' AND retention_ends_at <= NOW()
            ORDER BY retention_ends_at ASC
            LIMIT 1000
        """;

        return (await db.QueryAsync<GdprRetention>(sql)).ToList();
    }

    /// <summary>
    /// Redact personal data from audit events (soft delete via JSONB update).
    /// </summary>
    public async Task RedactAuditEventDetailsAsync(
        IDbConnection db,
        Guid eventId,
        CancellationToken ct = default)
    {
        const string sql = """
            UPDATE compliance.audit_events
            SET details = jsonb_set(
                jsonb_set(
                    COALESCE(details, '{}'::jsonb),
                    '{actor_email}',
                    '"<redacted>"'::jsonb
                ),
                '{customer_id}',
                '"<purged>"'::jsonb
            ),
            revision = revision + 1
            WHERE id = @EventId
        """;

        await db.ExecuteAsync(sql, new { EventId = eventId });
    }
}
