using System.Text.Json;
using Meezi.Core.Entities;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Microsoft.Extensions.DependencyInjection;

namespace Meezi.API.Services;

/// <summary>
/// Persists audit entries on a fresh, isolated <see cref="AppDbContext"/> so the
/// write never participates in (or rolls back with) the caller's transaction, and
/// swallows all failures — auditing must never break the recorded operation.
/// </summary>
public sealed class AuditLogService : IAuditLogService
{
    private static readonly JsonSerializerOptions DetailsJsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
    };

    private readonly ITenantContext _tenant;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<AuditLogService> _logger;

    public AuditLogService(
        ITenantContext tenant,
        IServiceScopeFactory scopeFactory,
        ILogger<AuditLogService> logger)
    {
        _tenant = tenant;
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    public async Task LogAsync(AuditEntry entry, CancellationToken ct = default)
    {
        try
        {
            var cafeId = _tenant.CafeId;
            if (string.IsNullOrEmpty(cafeId))
            {
                _logger.LogWarning(
                    "Skipping audit log '{Category}/{Action}' — no cafe context.",
                    entry.Category, entry.Action);
                return;
            }

            var log = new AuditLog
            {
                CafeId = cafeId,
                BranchId = entry.BranchId ?? _tenant.BranchId,
                Category = entry.Category,
                Action = entry.Action,
                EntityType = entry.EntityType,
                EntityId = entry.EntityId,
                ActorId = _tenant.UserId,
                ActorName = entry.ActorName,
                ActorRole = _tenant.Role?.ToString(),
                Summary = entry.Summary,
                DetailsJson = entry.Details is null
                    ? null
                    : JsonSerializer.Serialize(entry.Details, DetailsJsonOptions)
            };

            // Fresh scope → fresh DbContext (café-wide, unfiltered) so this write is
            // independent of the business operation's change-tracker and transaction.
            using var scope = _scopeFactory.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            db.AuditLogs.Add(log);
            await db.SaveChangesAsync(ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Failed to write audit log '{Category}/{Action}' for entity {EntityType}:{EntityId}.",
                entry.Category, entry.Action, entry.EntityType, entry.EntityId);
        }
    }
}
