using System.Security.Claims;
using audit_logs.Models;
using audit_logs.Models.DTO;
using audit_logs.Utils;
using MongoDB.Bson;

namespace audit_test.Repository;

using audit_logs.Repositories;
using audit.Model;
using audit_logs.Services;

public class AuditServiceIntegrationTests
{
    private AuditService _service;
    // private AuditRecordRepository _repository;

    [Order(1)]
    [SetUp]
    public void Setup()
    {
        IntegrationDb.Database.DropCollectionAsync("audit_records");
        //_repository = new AuditRecordRepository(IntegrationDb.Database);
        //_service = new AuditService(IntegrationDb.Database);
        _service = new AuditService(IntegrationDbReal.Database);
    }

    [Test]
    public async Task RecordAuditAsync_CreatesAndRetrievesAuditRecord()
    {
        // Arrange
        var changes = new List<AuditRecord.Change>
        {
            new AuditRecord.Change
                { Field = "Username", OldValue = "oldUser", NewValue = "newUser", ValueType = "String" }
        };
        AuditRecord auditLogData = new();
        auditLogData.EntityType = "User";
        auditLogData.RecordId = "1";
        auditLogData.UserId = "123";
        auditLogData.ActionType = "Update";
        auditLogData.ActionTime = DateTime.UtcNow;
        auditLogData.Reason = "Testing change detection";
        auditLogData.UserIpAddress = "127.0.0.1";
        auditLogData.Changes = changes;

        // Act
        await _service.RecordAuditAsync(auditLogData);


        // Retrieve all records to validate insertion
        var allRecords = await _service.AuditLogRepository.GetAllAuditRecordsAsync();

        // Assert
        Assert.AreEqual(1, allRecords.Count, "There should be exactly three audit record in the database.");
        var storedRecord = allRecords[0];
        Assert.AreEqual(auditLogData.EntityType, storedRecord.EntityType);
        Assert.AreEqual(auditLogData.RecordId, storedRecord.RecordId);
        Assert.AreEqual(auditLogData.UserId, storedRecord.UserId);
        Assert.AreEqual(auditLogData.ActionType, storedRecord.ActionType);
        Assert.AreEqual(auditLogData.Reason, storedRecord.Reason);
        //  Assert.That(storedRecord.Changes, Is.EquivalentTo(changes).Using(new ChangeComparer()), "Changes should match the input changes.");
    }

    [Test]
    public async Task GetAllAuditRecords_ReturnsAllRecords()
    {
        // Prepare multiple records
        var records = new List<AuditRecord>
        {
            new AuditRecord
            {
                EntityType = "User", RecordId = "1", UserId = "123", ActionType = "Create",
                ActionTime = DateTime.UtcNow, Reason = "Test Reason 1"
            },
            new AuditRecord
            {
                EntityType = "Admin", RecordId = "2", UserId = "456", ActionType = "Delete",
                ActionTime = DateTime.UtcNow, Reason = "Test Reason 2"
            }
        };

        foreach (var rec in records)
        {
            await _service.AuditLogRepository.InsertOneAsync(rec);
        }

        // Test retrieval method
        var allRecords = await _service.AuditLogRepository.GetAllAuditRecordsAsync();

        // Assert
        Assert.AreEqual(2, allRecords.Count, "All records should be retrieved.");
        Assert.IsTrue(allRecords.Exists(r => r.RecordId == "1" && r.EntityType == "User"));
        Assert.IsTrue(allRecords.Exists(r => r.RecordId == "2" && r.EntityType == "Admin"));
    }

    [Test]
    public async Task DetectChangesAsync_DetectsAndRecordsChanges() //este es el que da problemas
    {
        // Arrange
        // Arrange
        var user = new ClaimsPrincipal(new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, "testUser"),
            new Claim("IpAddress", "127.0.0.1")
        }));

        // Variables de auditoría
        AuditLogData auditLogData = new();
        auditLogData.OriginalJson = "{\"nhc\":\"C469\",\"person\":{\"admTime\":\"2024-11-26T12:03:36.276Z\",\"birthDate\":\"2024-11-02\",\"firstName\":\"Carlos Vi\",\"secondName\":\"Rey\",\"gender\":\"Male\",\"allergies\":\"\",\"ids\":{\"MR\":\"C469\"},\"historicalIds\":{}},\"unitId\":\"662760a96bdc7150b49fe29d\",\"admissionDate\":\"2024-11-26T12:03:36.276Z\",\"allergies\":\"\",\"diagnosis\":{\"optionType\":null,\"name\":\"Absceso amebiano del cerebro\",\"iconDefault\":null,\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":\"A06.6\",\"initDate\":null,\"endDate\":null},\"origin\":{\"optionType\":null,\"name\":\"3A ucip\",\"iconDefault\":null,\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":null,\"initDate\":null,\"endDate\":null},\"pointOfCareId\":null,\"languageBarrier\":\"\",\"originAux\":\"\",\"diagnosisAux\":\"Absceso amebiano del cerebro\"}";
      //  auditLogData.ModifiedJson =  "{\"nhc\":\"H987541\",\"patientLocation\":{\"unitName\":\"SMACST\",\"bed\":\"SMT 2\",\"room\":\"SMT 2\"},\"person\":{\"admTime\":\"2024-11-26T10:31:37.018Z\",\"birthDate\":\"2024-11-01\",\"firstName\":\"Carlos\",\"secondName\":\"Borja\",\"gender\":\"Male\",\"allergies\":[{\"optionType\":null,\"name\":\"Sí\",\"iconDefault\":\"icCheck\",\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":null,\"initDate\":null,\"endDate\":null}],\"ids\":{\"MR\":\"H987541\"},\"historicalIds\":{}},\"unitId\":\"662760a96bdc7150b49fe29d\",\"admissionDate\":\"2024-11-26T10:31:37.018Z\",\"allergies\":[{\"optionType\":null,\"name\":\"Sí\",\"iconDefault\":\"icCheck\",\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":null,\"initDate\":null,\"endDate\":null}],\"diagnosis\":{\"optionType\":null,\"name\":\"Absceso amebiano del cerebro\",\"iconDefault\":null,\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":\"A06.6\",\"initDate\":null,\"endDate\":null},\"origin\":{\"optionType\":null,\"name\":\"3A ucip\",\"iconDefault\":null,\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":null,\"initDate\":null,\"endDate\":null},\"pointOfCareId\":\"662761fc6bdc7150b49fe29f\",\"languageBarrier\":\"\",\"originAux\":\"\",\"diagnosisAux\":\"Absceso amebiano del cerebro\"}";
      auditLogData.ModifiedJson = null;//"{\"nhc\":\"C469\",\"person\":{\"admTime\":\"2024-11-26T12:03:36.276Z\",\"birthDate\":\"2024-11-01\",\"firstName\":\"Carlos V\",\"secondName\":\"Rey\",\"gender\":\"Male\",\"allergies\":\"\",\"ids\":{\"MR\":\"C469\"},\"historicalIds\":{}},\"unitId\":\"662760a96bdc7150b49fe29d\",\"admissionDate\":\"2024-11-26T12:03:36.276Z\",\"allergies\":\"\",\"diagnosis\":{\"optionType\":null,\"name\":\"Absceso amebiano del cerebro\",\"iconDefault\":null,\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":\"A06.6\",\"initDate\":null,\"endDate\":null},\"origin\":{\"optionType\":null,\"name\":\"3A ucip\",\"iconDefault\":null,\"iconLight\":null,\"iconDark\":null,\"color\":null,\"bgColor\":null,\"description\":null,\"initDate\":null,\"endDate\":null},\"pointOfCareId\":null,\"languageBarrier\":\"\",\"originAux\":\"\",\"diagnosisAux\":\"Absceso amebiano del cerebro\"}";
        auditLogData.EntityType = "User";
        auditLogData.RecordId = "1";
        auditLogData.UserId = "123";
        auditLogData.ActionType = "Update";
        auditLogData.ActionTime = DateTime.UtcNow;
        auditLogData.Reason = "Testing change detection";
        auditLogData.UserIpAddress = "127.0.0.1";

        // Act - Llamar a DetectChangesAsync para que identifique cambios
        //await _service.CreateAuditLogAsync(auditLogData);
        await _service.CreateAuditLogAsync(auditLogData);

        
        // Retrieve all records to validate insertion
        var allRecords = await _service.AuditLogRepository.GetAllAuditRecordsAsync();

        // Assert
        Assert.AreEqual(1, allRecords.Count, "Debe haber exactamente un registro de auditoría en la base de datos.");
        var storedRecord = allRecords[0];
        Assert.AreEqual(auditLogData.EntityType, storedRecord.EntityType);
        Assert.AreEqual(auditLogData.RecordId, storedRecord.RecordId);
        Assert.AreEqual(auditLogData.UserId, storedRecord.UserId);
        Assert.AreEqual(auditLogData.ActionType, storedRecord.ActionType);
        Assert.AreEqual(auditLogData.Reason, storedRecord.Reason);

        // Verificar que los cambios detectados sean correctos
        Assert.That(storedRecord.Changes.Count, Is.EqualTo(10), "Debería detectar tres cambios.");


    }

    [Test]
    public async Task GetAuditLogsAsync_ReturnsPaginatedRecords()
    {
        // Arrange - Insert multiple audit records to test pagination
        var records = new List<AuditRecord>();
        for (int i = 1; i <= 20; i++)
        {
            records.Add(new AuditRecord
            {
                EntityType = "TestEntity",
                RecordId = i.ToString(),
                UserId = "User" + i,
                ActionType = "Create",
                ActionTime = DateTime.UtcNow,
                Reason = "Test Reason " + i,
                Changes = new List<AuditRecord.Change>
                {
                    new AuditRecord.Change
                    {
                        Field = "Field" + i,
                        OldValue = "OldValue" + i,
                        NewValue = "NewValue" + i,
                        ValueType = "String"
                    }
                }
            });
        }

        foreach (var record in records)
        {
            await _service.AuditLogRepository.InsertOneAsync(record);
        }

        // Act - Retrieve the first page with a page size of 5
        int pageNumber = 1;
        int pageSize = 5;
        SpecificAuditLogFilterDto auditLogParametersDto = new SpecificAuditLogFilterDto();
        auditLogParametersDto.pageNumber=1;
        auditLogParametersDto.pageSize = 5;
        var paginatedResult = await _service.GetAuditLogsBySpecificFilterAsync(auditLogParametersDto);

        // Assert - Check that the pagination returns the correct number of records and page information
        Assert.AreEqual(pageSize, paginatedResult.Records.Count(), "The number of records should match the page size.");
        Assert.AreEqual(20, paginatedResult.TotalRecords,"The total number of records should match the inserted records.");
        Assert.AreEqual(4, paginatedResult.TotalPages, "The total pages should be calculated correctly.");
        Assert.AreEqual(pageNumber, paginatedResult.CurrentPage, "The current page should be correct.");

        // Further assertions to check that the records and changes are the expected ones
        var firstRecord = paginatedResult.Records.First();
        Assert.AreEqual("TestEntity", firstRecord.EntityType);
        Assert.AreEqual("1", firstRecord.RecordId);
        Assert.AreEqual("User1", firstRecord.UserId);
        Assert.AreEqual("Create", firstRecord.ActionType);

        // Verify the changes for the first record
        Assert.IsNotNull(firstRecord.Changes);
        Assert.AreEqual(1, firstRecord.Changes.Count, "There should be exactly one change in the first record.");
        var firstChange = firstRecord.Changes.First();
        Assert.AreEqual("Field1", firstChange.Field);
        Assert.AreEqual("OldValue1", firstChange.OldValue);
        Assert.AreEqual("NewValue1", firstChange.NewValue);
        Assert.AreEqual("String", firstChange.ValueType);
    }

    [Test]
    public async Task CreateAuditLogAsync_CreatesCorrectAuditLog()
    {
        // Arrange
        var user = new ClaimsPrincipal(new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, "testUser"),
            new Claim("IpAddress", "127.0.0.1")
        }));

        var dataOriginal = new
        {
            Id = ObjectId.GenerateNewId(),
            Name = "OldName",
            Email = "old@example.com"
        };

        var dataModified = new
        {
            Id = dataOriginal.Id,
            Name = "NewName",
            Email = "new@example.com"
        };

        string reason = "Updating user details";

        // Act
        await _service.CreateAuditLogAsync(user, dataOriginal, dataModified, reason);

        // Assert - Retrieve audit logs to validate
        var allRecords = await _service.AuditLogRepository.GetAllAuditRecordsAsync();

        Assert.AreEqual(1, allRecords.Count, "There should be exactly one audit log entry.");
        var storedRecord = allRecords[0];

        // Validate stored record
        Assert.AreEqual("AnonymousType", storedRecord.EntityType);
        Assert.AreEqual(dataOriginal.Id.ToString(), storedRecord.RecordId);
        Assert.AreEqual("testUser", storedRecord.UserId);
        Assert.AreEqual("update", storedRecord.ActionType);
        Assert.AreEqual(reason, storedRecord.Reason);
        Assert.AreEqual("127.0.0.1", storedRecord.UserIpAddress);

        // Validate changes
        Assert.AreEqual(2, storedRecord.Changes.Count, "There should be exactly two detected changes.");
        var nameChange = storedRecord.Changes.Find(c => c.Field == "Name");
        Assert.NotNull(nameChange, "There should be a change in the 'Name' field.");
        Assert.AreEqual("OldName", nameChange.OldValue.AsString);
        Assert.AreEqual("NewName", nameChange.NewValue.AsString);
        Assert.AreEqual("string", nameChange.ValueType);

        var emailChange = storedRecord.Changes.Find(c => c.Field == "Email");
        Assert.NotNull(emailChange, "There should be a change in the 'Email' field.");
        Assert.AreEqual("old@example.com", emailChange.OldValue.AsString);
        Assert.AreEqual("new@example.com", emailChange.NewValue.AsString);
        Assert.AreEqual("string", emailChange.ValueType);
    }

    [Test]
    public async Task CreateAuditLogAsync_WithNullIpAndDataOriginal_CreatesCorrectAuditLog()
    {
        // Arrange
        var user = new ClaimsPrincipal(new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, "testUser"),
            // IP Address claim is not included to simulate a null IP scenario
        }));

        // Set dataOriginal to null to simulate scenario
        object dataOriginal = null;

        var dataModified = new
        {
            Id = ObjectId.GenerateNewId(),
            Name = "NewName",
            Email = "new@example.com"
        };

        string reason = "Creating new user";

        // Act
        await _service.CreateAuditLogAsync(user, dataOriginal, dataModified, reason);

        // Assert - Retrieve audit logs to validate
        var allRecords = await _service.AuditLogRepository.GetAllAuditRecordsAsync();

        Assert.AreEqual(1, allRecords.Count, "There should be exactly one audit log entry.");
        var storedRecord = allRecords[0];

        // Validate stored record
        Assert.AreEqual("AnonymousType", storedRecord.EntityType);
        Assert.AreEqual(dataModified.Id.ToString(), storedRecord.RecordId);
        Assert.AreEqual("testUser", storedRecord.UserId);
        Assert.AreEqual("create", storedRecord.ActionType); // Action type should be 'create' as original data is null
        Assert.AreEqual(reason, storedRecord.Reason);

        // Validate changes - expecting changes in name and email as it's a creation
        Assert.AreEqual(3, storedRecord.Changes.Count, "There should be exactly two detected changes.");
        var nameChange = storedRecord.Changes.Find(c => c.Field == "Name");
        Assert.NotNull(nameChange, "There should be a change in the 'Name' field.");
        Assert.IsTrue(nameChange.OldValue.IsBsonNull, "Old value should be null for new creations.");

        Assert.AreEqual("NewName", nameChange.NewValue.AsString);
        Assert.AreEqual("string", nameChange.ValueType);

        var emailChange = storedRecord.Changes.Find(c => c.Field == "Email");
        Assert.NotNull(emailChange, "There should be a change in the 'Email' field.");
        Assert.IsTrue(emailChange.OldValue.IsBsonNull, "Old value should be null for new creations.");
        Assert.AreEqual("new@example.com", emailChange.NewValue.AsString);
        Assert.AreEqual("string", emailChange.ValueType);
    }

    [Test]
    public async Task CreateAuditLogAsync_WithNullIpAndDataModified_CreatesCorrectAuditLog()
    {
        // Arrange
        var user = new ClaimsPrincipal(new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, "testUser"),
            // IP Address claim is not included to simulate a null IP scenario
        }));

        // Set dataModified to null to simulate the scenario
        object? dataModified = null;

        var dataOriginal = new
        {
            Id = ObjectId.GenerateNewId(),
            Name = "OldName",
            Email = "old@example.com"
        };

        string reason = "Deleting user";

        // Act
        await _service.CreateAuditLogAsync(user, dataOriginal, dataModified, reason);

        // Assert - Retrieve audit logs to validate
        var allRecords = await _service.AuditLogRepository.GetAllAuditRecordsAsync();

        Assert.AreEqual(1, allRecords.Count, "There should be exactly one audit log entry.");
        var storedRecord = allRecords[0];

        // Validate stored record
        Assert.AreEqual("AnonymousType", storedRecord.EntityType);
        Assert.AreEqual(dataOriginal.Id.ToString(), storedRecord.RecordId);
        Assert.AreEqual("testUser", storedRecord.UserId);
        Assert.AreEqual("delete", storedRecord.ActionType); // Action type should be 'delete' as modified data is null
        Assert.AreEqual(reason, storedRecord.Reason);

        // Validate changes - expecting no changes as it's a deletion
        Assert.AreEqual(3, storedRecord.Changes.Count, "There should be exactly two detected changes.");
        var nameChange = storedRecord.Changes.Find(c => c.Field == "Name");
        Assert.NotNull(nameChange, "There should be a change in the 'Name' field.");
        Assert.AreEqual("OldName", nameChange.OldValue.AsString, "Old value should be 'OldName'.");
        Assert.IsTrue(nameChange.NewValue.IsBsonNull, "New value should be null for deletions.");
        Assert.AreEqual("string", nameChange.ValueType);

        var emailChange = storedRecord.Changes.Find(c => c.Field == "Email");
        Assert.NotNull(emailChange, "There should be a change in the 'Email' field.");
        Assert.AreEqual("old@example.com", emailChange.OldValue.AsString, "Old value should be 'old@example.com'.");
        Assert.IsTrue(emailChange.NewValue.IsBsonNull, "New value should be null for deletions.");
        Assert.AreEqual("string", emailChange.ValueType);
    }
}