using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Shared;
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;

namespace ZB.MOM.WW.CBDD.Tests;

public class AttributeTests
{
    private readonly ConcurrentDictionary<string, ushort> _keyMap = new(StringComparer.OrdinalIgnoreCase);
    private readonly ConcurrentDictionary<ushort, string> _keys = new();

    /// <summary>
    ///     Initializes lookup maps used by attribute mapper tests.
    /// </summary>
    public AttributeTests()
    {
        ushort id = 1;
        string[] keys = ["_id", "display_name", "age", "location", "0", "1"];
        foreach (string key in keys)
        {
            _keyMap[key] = id;
            _keys[id] = key;
            id++;
        }
    }

    // Use full path for mapper until we are sure of the namespace
    private ZB_MOM_WW_CBDD_Shared_AnnotatedUserMapper CreateMapper()
    {
        return new ZB_MOM_WW_CBDD_Shared_AnnotatedUserMapper();
    }

    /// <summary>
    ///     Verifies table attribute mapping resolves the expected collection name.
    /// </summary>
    [Fact]
    public void Test_Table_Attribute_Mapping()
    {
        // Verify that the generated mapper has the correct collection name
        var mapper = CreateMapper();
        mapper.CollectionName.ShouldBe("test.custom_users");
    }

    /// <summary>
    ///     Verifies required attribute validation is enforced.
    /// </summary>
    [Fact]
    public void Test_Required_Validation()
    {
        var mapper = CreateMapper();
        var user = new AnnotatedUser { Name = "" }; // Required name is empty
        var writer = new BsonSpanWriter(new byte[1024], _keyMap);

        var thrown = false;
        try
        {
            mapper.Serialize(user, writer);
        }
        catch (ValidationException)
        {
            thrown = true;
        }

        thrown.ShouldBeTrue("Should throw ValidationException for empty Name.");
    }

    /// <summary>
    ///     Verifies string length attribute validation is enforced.
    /// </summary>
    [Fact]
    public void Test_StringLength_Validation()
    {
        var mapper = CreateMapper();
        var user = new AnnotatedUser { Name = "Jo" }; // Too short
        var writer = new BsonSpanWriter(new byte[1024], _keyMap);

        var thrown = false;
        try
        {
            mapper.Serialize(user, writer);
        }
        catch (ValidationException)
        {
            thrown = true;
        }

        thrown.ShouldBeTrue("Should throw ValidationException for Name too short.");

        user.Name = new string('A', 51); // Too long
        thrown = false;
        try
        {
            mapper.Serialize(user, writer);
        }
        catch (ValidationException)
        {
            thrown = true;
        }

        thrown.ShouldBeTrue("Should throw ValidationException for Name too long.");
    }

    /// <summary>
    ///     Verifies range attribute validation is enforced.
    /// </summary>
    [Fact]
    public void Test_Range_Validation()
    {
        var mapper = CreateMapper();
        var user = new AnnotatedUser { Name = "John", Age = 200 }; // Out of range
        var writer = new BsonSpanWriter(new byte[1024], _keyMap);

        var thrown = false;
        try
        {
            mapper.Serialize(user, writer);
        }
        catch (ValidationException)
        {
            thrown = true;
        }

        thrown.ShouldBeTrue("Should throw ValidationException for Age out of range.");
    }

    /// <summary>
    ///     Verifies column attribute maps to the expected BSON field name.
    /// </summary>
    [Fact]
    public void Test_Column_Name_Mapping()
    {
        var mapper = CreateMapper();
        var user = new AnnotatedUser { Name = "John", Age = 30 };
        var buffer = new byte[1024];
        var writer = new BsonSpanWriter(buffer, _keyMap);

        mapper.Serialize(user, writer);

        var reader = new BsonSpanReader(buffer, _keys);
        reader.ReadDocumentSize();

        var foundDisplayName = false;
        while (reader.Remaining > 0)
        {
            var type = reader.ReadBsonType();
            if (type == BsonType.EndOfDocument) break;

            string name = reader.ReadElementHeader();
            if (name == "display_name") foundDisplayName = true;
            reader.SkipValue(type);
        }

        foundDisplayName.ShouldBeTrue("BSON field name should be 'display_name' from [Column] attribute.");
    }

    /// <summary>
    ///     Verifies not-mapped attribute excludes properties from BSON serialization.
    /// </summary>
    [Fact]
    public void Test_NotMapped_Attribute()
    {
        var mapper = CreateMapper();
        var user = new AnnotatedUser { Name = "John", Age = 30 };
        var buffer = new byte[1024];
        var writer = new BsonSpanWriter(buffer, _keyMap);

        mapper.Serialize(user, writer);

        var reader = new BsonSpanReader(buffer, _keys);
        reader.ReadDocumentSize();

        var foundComputed = false;
        while (reader.Remaining > 0)
        {
            var type = reader.ReadBsonType();
            if (type == BsonType.EndOfDocument) break;

            string name = reader.ReadElementHeader();
            if (name == "ComputedInfo") foundComputed = true;
            reader.SkipValue(type);
        }

        foundComputed.ShouldBeFalse("ComputedInfo should not be mapped to BSON.");
    }
}