using Microsoft.EntityFrameworkCore;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Infrastructure.Persistence;

namespace OmsorgCore.Tests.Persistence;

public class AuditSaveChangesInterceptorTests
{
    private class FakeCurrentUserService : ICurrentUserService
    {
        public Guid? UserId { get; set; }
        public bool IsAuthenticated { get; set; }
        public string? Username { get; set; }
        public string? RoleName { get; set; }
        public string? IpAddress { get; set; }
        public Guid? EmployeeId { get; set; }
    }

    private static OmsorgCoreDbContext CreateContext(ICurrentUserService currentUserService)
    {
        var options = new DbContextOptionsBuilder<OmsorgCoreDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .AddInterceptors(new AuditSaveChangesInterceptor(currentUserService))
            .Options;

        return new OmsorgCoreDbContext(options);
    }

    private static Employee CreateEmployee() => new()
    {
        FirstName = "Anna",
        LastName = "Muster",
        Status = "active",
        EmploymentType = "fulltime"
    };

    [Fact]
    public async Task SaveChanges_OnAdd_CreatesAuditEntryWithActor()
    {
        var currentUser = new FakeCurrentUserService { IsAuthenticated = true, UserId = Guid.NewGuid(), Username = "sabina" };
        await using var db = CreateContext(currentUser);

        var employee = CreateEmployee();
        db.Employees.Add(employee);
        await db.SaveChangesAsync();

        var entry = Assert.Single(db.AuditLogEntries.Local);
        Assert.Equal("Created", entry.Action);
        Assert.Equal(nameof(Employee), entry.EntityType);
        Assert.Equal(employee.Id, entry.EntityId);
        Assert.Equal(currentUser.UserId, entry.ActorUserId);
        Assert.Equal("sabina", entry.ActorUsername);
        Assert.Contains("Anna", entry.Details);
    }

    [Fact]
    public async Task SaveChanges_OnFieldUpdate_CapturesOnlyChangedFieldsAsDiff()
    {
        var currentUser = new FakeCurrentUserService { IsAuthenticated = true, UserId = Guid.NewGuid(), Username = "sabina" };
        await using var db = CreateContext(currentUser);

        var employee = CreateEmployee();
        db.Employees.Add(employee);
        await db.SaveChangesAsync();
        db.AuditLogEntries.RemoveRange(db.AuditLogEntries.Local);

        employee.LastName = "Neu";
        await db.SaveChangesAsync();

        var entry = Assert.Single(db.AuditLogEntries.Local);
        Assert.Equal("Updated", entry.Action);
        Assert.Contains("LastName", entry.Details);
        Assert.DoesNotContain("FirstName", entry.Details);
    }

    [Fact]
    public async Task SaveChanges_OnSoftDelete_IsCapturedAsDeletedAction()
    {
        var currentUser = new FakeCurrentUserService { IsAuthenticated = true, UserId = Guid.NewGuid(), Username = "sabina" };
        await using var db = CreateContext(currentUser);

        var employee = CreateEmployee();
        db.Employees.Add(employee);
        await db.SaveChangesAsync();
        db.AuditLogEntries.RemoveRange(db.AuditLogEntries.Local);

        employee.IsDeleted = true;
        await db.SaveChangesAsync();

        var entry = Assert.Single(db.AuditLogEntries.Local);
        Assert.Equal("Deleted", entry.Action);
    }

    [Fact]
    public async Task SaveChanges_OnRedactedField_ReplacesValueWithPlaceholder()
    {
        var currentUser = new FakeCurrentUserService { IsAuthenticated = true, UserId = Guid.NewGuid(), Username = "admin" };
        await using var db = CreateContext(currentUser);

        var role = new Role { Name = "Administrator" };
        var user = new User { Username = "admin", PasswordHash = "super-secret-hash", Role = role, RoleId = role.Id };
        db.Roles.Add(role);
        db.Users.Add(user);
        await db.SaveChangesAsync();

        var entry = Assert.Single(db.AuditLogEntries.Local.Where(e => e.EntityType == nameof(User)));
        Assert.DoesNotContain("super-secret-hash", entry.Details);
        Assert.Contains("***redacted***", entry.Details);
    }

    [Fact]
    public async Task SaveChanges_WithoutAuthenticatedActor_LeavesActorFieldsNull()
    {
        var currentUser = new FakeCurrentUserService { IsAuthenticated = false };
        await using var db = CreateContext(currentUser);

        db.Employees.Add(CreateEmployee());
        await db.SaveChangesAsync();

        var entry = Assert.Single(db.AuditLogEntries.Local);
        Assert.Null(entry.ActorUserId);
        Assert.Null(entry.ActorUsername);
    }
}
