using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Metadata;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ZB.MOM.WW.CBDD.Shared
{
    // --- Basic Entities ---

    public class User
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "";
        /// <summary>
        /// Gets or sets the age.
        /// </summary>
        public int Age { get; set; }
    }

    // --- Complex Entities (Nested) ---

    public class ComplexUser
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        [BsonId]
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "";

        // Direct nested object
        /// <summary>
        /// Gets or sets the main address.
        /// </summary>
        public Address MainAddress { get; set; } = new();

        // Collection of nested objects
        /// <summary>
        /// Gets or sets the other addresses.
        /// </summary>
        public List<Address> OtherAddresses { get; set; } = new();

        // Primitive collection
        /// <summary>
        /// Gets or sets the tags.
        /// </summary>
        public List<string> Tags { get; set; } = new();

        /// <summary>
        /// Gets or sets the secret.
        /// </summary>
        [BsonIgnore]
        public string Secret { get; set; } = "";
    }

    public class Address
    {
        /// <summary>
        /// Gets or sets the street.
        /// </summary>
        public string Street { get; set; } = "";
        /// <summary>
        /// Gets or sets the city.
        /// </summary>
        public City City { get; set; } = new(); // Depth 2
    }

    public class City
    {
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "";
        /// <summary>
        /// Gets or sets the zip code.
        /// </summary>
        public string ZipCode { get; set; } = "";
    }

    // --- Primary Key Test Entities ---

    public class IntEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string? Name { get; set; }
    }

    public class StringEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public required string Id { get; set; }
        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        public string? Value { get; set; }
    }

    public class GuidEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public Guid Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string? Name { get; set; }
    }

    /// <summary>
    /// Entity with string key NOT named "Id" - tests custom key name support
    /// </summary>
    public class CustomKeyEntity
    {
        /// <summary>
        /// Gets or sets the code.
        /// </summary>
        [System.ComponentModel.DataAnnotations.Key]
        public required string Code { get; set; }
        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        public string? Description { get; set; }
    }

    // --- Multi-collection / Auto-init entities ---

    public class AutoInitEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
    }

    public class Person
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "";
        /// <summary>
        /// Gets or sets the age.
        /// </summary>
        public int Age { get; set; }
    }

    public class Product
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        public string Title { get; set; } = "";
        /// <summary>
        /// Gets or sets the price.
        /// </summary>
        public decimal Price { get; set; }
    }

    public class AsyncDoc
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "";
    }

    public class SchemaUser
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "";
        /// <summary>
        /// Gets or sets the address.
        /// </summary>
        public Address Address { get; set; } = new();
    }

    public class VectorEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        public string Title { get; set; } = "";
        /// <summary>
        /// Gets or sets the embedding.
        /// </summary>
        public float[] Embedding { get; set; } = Array.Empty<float>();
    }

    public class GeoEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "";
        /// <summary>
        /// Gets or sets the location.
        /// </summary>
        public (double Latitude, double Longitude) Location { get; set; }
    }

    public record OrderId(string Value)
    {
        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        public OrderId() : this(string.Empty) { }
    }

    public class OrderIdConverter : ValueConverter<OrderId, string>
    {
        /// <inheritdoc />
        public override string ConvertToProvider(OrderId model) => model?.Value ?? string.Empty;
        /// <inheritdoc />
        public override OrderId ConvertFromProvider(string provider) => new OrderId(provider);
    }

    public class Order
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public OrderId Id { get; set; } = null!;
        /// <summary>
        /// Gets or sets the customer name.
        /// </summary>
        public string CustomerName { get; set; } = "";
    }

    public class TestDocument
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the category.
        /// </summary>
        public string Category { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the amount.
        /// </summary>
        public int Amount { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
    }

    public class OrderDocument
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the item name.
        /// </summary>
        public string ItemName { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the quantity.
        /// </summary>
        public int Quantity { get; set; }
    }

    public class OrderItem
    {
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the price.
        /// </summary>
        public int Price { get; set; }
    }

    public class ComplexDocument
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        public string Title { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the shipping address.
        /// </summary>
        public Address ShippingAddress { get; set; } = new();
        /// <summary>
        /// Gets or sets the items.
        /// </summary>
        public List<OrderItem> Items { get; set; } = new();
    }

    [Table("custom_users", Schema = "test")]
    public class AnnotatedUser
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        [Key]
        public ObjectId Id { get; set; }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        [Required]
        [Column("display_name")]
        [StringLength(50, MinimumLength = 3)]
        public string Name { get; set; } = "";

        /// <summary>
        /// Gets or sets the age.
        /// </summary>
        [Range(0, 150)]
        public int Age { get; set; }

        /// <summary>
        /// Gets the computed info.
        /// </summary>
        [NotMapped]
        public string ComputedInfo => $"{Name} ({Age})";

        /// <summary>
        /// Gets or sets the location.
        /// </summary>
        [Column(TypeName = "geopoint")]
        public (double Lat, double Lon) Location { get; set; }
    }
    public class PersonV2
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the age.
        /// </summary>
        public int Age { get; set; }
    }

    /// <summary>
    /// Entity used to test DbContext inheritance
    /// </summary>
    public class ExtendedEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        public string Description { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the created at.
        /// </summary>
        public DateTime CreatedAt { get; set; }
    }

    // ===== SOURCE GENERATOR FEATURE TESTS =====

    /// <summary>
    /// Base entity with Id property - test inheritance
    /// </summary>
    public class BaseEntityWithId
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the created at.
        /// </summary>
        public DateTime CreatedAt { get; set; }
    }

    /// <summary>
    /// Derived entity that inherits Id from base class
    /// </summary>
    public class DerivedEntity : BaseEntityWithId
    {
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        public string Description { get; set; } = string.Empty;
    }

    /// <summary>
    /// Entity with computed getter-only properties (should be excluded from serialization)
    /// </summary>
    public class EntityWithComputedProperties
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the first name.
        /// </summary>
        public string FirstName { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the last name.
        /// </summary>
        public string LastName { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the birth year.
        /// </summary>
        public int BirthYear { get; set; }

        // Computed properties - should NOT be serialized
        /// <summary>
        /// Gets the full name.
        /// </summary>
        public string FullName => $"{FirstName} {LastName}";
        /// <summary>
        /// Gets the age.
        /// </summary>
        public int Age => DateTime.Now.Year - BirthYear;
        /// <summary>
        /// Gets the display info.
        /// </summary>
        public string DisplayInfo => $"{FullName} (Age: {Age})";
    }

    /// <summary>
    /// Entity with advanced collection types (HashSet, ISet, LinkedList, etc.)
    /// </summary>
    public class EntityWithAdvancedCollections
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;

        // Various collection types that should all be recognized
        /// <summary>
        /// Gets or sets the tags.
        /// </summary>
        public HashSet<string> Tags { get; set; } = new();
        /// <summary>
        /// Gets or sets the numbers.
        /// </summary>
        public ISet<int> Numbers { get; set; } = new HashSet<int>();
        /// <summary>
        /// Gets or sets the history.
        /// </summary>
        public LinkedList<string> History { get; set; } = new();
        /// <summary>
        /// Gets or sets the pending items.
        /// </summary>
        public Queue<string> PendingItems { get; set; } = new();
        /// <summary>
        /// Gets or sets the undo stack.
        /// </summary>
        public Stack<string> UndoStack { get; set; } = new();

        // Nested objects in collections
        /// <summary>
        /// Gets or sets the addresses.
        /// </summary>
        public HashSet<Address> Addresses { get; set; } = new();
        /// <summary>
        /// Gets or sets the favorite cities.
        /// </summary>
        public ISet<City> FavoriteCities { get; set; } = new HashSet<City>();
    }

    /// <summary>
    /// Entity with private setters (requires reflection-based deserialization)
    /// </summary>
    public class EntityWithPrivateSetters
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; private set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; private set; } = string.Empty;
        /// <summary>
        /// Gets or sets the age.
        /// </summary>
        public int Age { get; private set; }
        /// <summary>
        /// Gets or sets the created at.
        /// </summary>
        public DateTime CreatedAt { get; private set; }

        // Factory method for creation
        /// <summary>
        /// Executes the create operation.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="age">The age.</param>
        public static EntityWithPrivateSetters Create(string name, int age)
        {
            return new EntityWithPrivateSetters
            {
                Id = ObjectId.NewObjectId(),
                Name = name,
                Age = age,
                CreatedAt = DateTime.UtcNow
            };
        }
    }

    /// <summary>
    /// Entity with init-only setters (can use object initializer)
    /// </summary>
    public class EntityWithInitSetters
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; init; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public required string Name { get; init; }
        /// <summary>
        /// Gets or sets the age.
        /// </summary>
        public int Age { get; init; }
        /// <summary>
        /// Gets or sets the created at.
        /// </summary>
        public DateTime CreatedAt { get; init; }
    }

    // ========================================
    // Circular Reference Test Entities
    // ========================================

    /// <summary>
    /// Employee with self-referencing via ObjectIds (organizational hierarchy)
    /// Tests: self-reference using referencing (BEST PRACTICE)
    /// Recommended: Avoids embedding which can lead to large/circular documents
    /// </summary>
    public class Employee
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the department.
        /// </summary>
        public string Department { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the manager id.
        /// </summary>
        public ObjectId? ManagerId { get; set; } // Reference to manager
        /// <summary>
        /// Gets or sets the direct report ids.
        /// </summary>
        public List<ObjectId>? DirectReportIds { get; set; } // References to direct reports (best practice)
    }

    /// <summary>
    /// Category with referenced products (N-N using ObjectId references)
    /// Tests: N-N relationships using referencing (BEST PRACTICE for document databases)
    /// Recommended: Avoids large documents, better for queries and updates
    /// </summary>
    public class CategoryRef
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the description.
        /// </summary>
        public string Description { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the product ids.
        /// </summary>
        public List<ObjectId>? ProductIds { get; set; } // Only IDs - no embedding
    }

    /// <summary>
    /// Product with referenced categories (N-N using ObjectId references)
    /// Tests: N-N relationships using referencing (BEST PRACTICE for document databases)
    /// Recommended: Avoids large documents, better for queries and updates
    /// </summary>
    public class ProductRef
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the price.
        /// </summary>
        public decimal Price { get; set; }
        /// <summary>
        /// Gets or sets the category ids.
        /// </summary>
        public List<ObjectId>? CategoryIds { get; set; } // Only IDs - no embedding
    }

    // ========================================
    // Nullable String Key Test (UuidEntity scenario)
    // ========================================

    /// <summary>
    /// Base entity class that simulates CleanCore's BaseEntity{TId, TEntity}
    /// This is the root of the hierarchy that causes the generator bug
    /// </summary>
    public abstract class MockBaseEntity<TId, TEntity>
        where TId : IEquatable<TId>
        where TEntity : class
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        [System.ComponentModel.DataAnnotations.Key]
        public virtual TId? Id { get; set; }

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        protected MockBaseEntity() { }

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="id">The id.</param>
        protected MockBaseEntity(TId? id)
        {
            Id = id;
        }
    }

    /// <summary>
    /// Simulates CleanCore's UuidEntity{TEntity} which inherits from BaseEntity{string, TEntity}
    /// Tests the bug where generator incorrectly chooses ObjectIdMapperBase instead of StringMapperBase
    /// when the Id property is inherited and nullable
    /// </summary>
    public abstract class MockUuidEntity<TEntity> : MockBaseEntity<string, TEntity>
        where TEntity : class
    {
        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        protected MockUuidEntity() : base() { }

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="id">The id.</param>
        protected MockUuidEntity(string? id) : base(id) { }
    }

    /// <summary>
    /// Concrete entity that inherits from MockUuidEntity, simulating Counter from CleanCore
    /// This is the actual entity that will be stored in the collection
    /// </summary>
    public class MockCounter : MockUuidEntity<MockCounter>
    {
        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        public MockCounter() : base() { }

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="id">The id.</param>
        public MockCounter(string? id) : base(id) { }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        public int Value { get; set; }
    }

    /// <summary>
    /// Entity for testing temporal types: DateTimeOffset, TimeSpan, DateOnly, TimeOnly
    /// </summary>
    public class TemporalEntity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        [Key]
        public ObjectId Id { get; set; }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = string.Empty;

        // DateTime types
        /// <summary>
        /// Gets or sets the created at.
        /// </summary>
        public DateTime CreatedAt { get; set; }
        /// <summary>
        /// Gets or sets the updated at.
        /// </summary>
        public DateTimeOffset UpdatedAt { get; set; }
        /// <summary>
        /// Gets or sets the last accessed at.
        /// </summary>
        public DateTimeOffset? LastAccessedAt { get; set; }

        // TimeSpan
        /// <summary>
        /// Gets or sets the duration.
        /// </summary>
        public TimeSpan Duration { get; set; }
        /// <summary>
        /// Gets or sets the optional duration.
        /// </summary>
        public TimeSpan? OptionalDuration { get; set; }

        // DateOnly and TimeOnly (.NET 6+)
        /// <summary>
        /// Gets or sets the birth date.
        /// </summary>
        public DateOnly BirthDate { get; set; }
        /// <summary>
        /// Gets or sets the anniversary.
        /// </summary>
        public DateOnly? Anniversary { get; set; }

        /// <summary>
        /// Gets or sets the opening time.
        /// </summary>
        public TimeOnly OpeningTime { get; set; }
        /// <summary>
        /// Gets or sets the closing time.
        /// </summary>
        public TimeOnly? ClosingTime { get; set; }
    }
}
