using System.Linq.Expressions;
using ZB.MOM.WW.CBDD.Core.Indexing;

namespace ZB.MOM.WW.CBDD.Core.Metadata;

public class EntityTypeBuilder<T> where T : class
{
    /// <summary>
    ///     Gets the configured collection name for the entity type.
    /// </summary>
    public string? CollectionName { get; private set; }

    /// <summary>
    ///     Gets the configured indexes for the entity type.
    /// </summary>
    public List<IndexBuilder<T>> Indexes { get; } = new();

    /// <summary>
    ///     Gets the primary key selector expression.
    /// </summary>
    public LambdaExpression? PrimaryKeySelector { get; private set; }

    /// <summary>
    ///     Gets a value indicating whether the primary key value is generated on add.
    /// </summary>
    public bool ValueGeneratedOnAdd { get; private set; }

    /// <summary>
    ///     Gets the configured primary key property name.
    /// </summary>
    public string? PrimaryKeyName { get; private set; }

    /// <summary>
    ///     Gets the configured property converter types keyed by property name.
    /// </summary>
    public Dictionary<string, Type> PropertyConverters { get; } = new();

    /// <summary>
    ///     Sets the collection name for the entity type.
    /// </summary>
    /// <param name="name">The collection name.</param>
    /// <returns>The current entity type builder.</returns>
    public EntityTypeBuilder<T> ToCollection(string name)
    {
        CollectionName = name;
        return this;
    }

    /// <summary>
    ///     Adds an index for the specified key selector.
    /// </summary>
    /// <typeparam name="TKey">The key type.</typeparam>
    /// <param name="keySelector">The key selector expression.</param>
    /// <param name="name">The optional index name.</param>
    /// <param name="unique">A value indicating whether the index is unique.</param>
    /// <returns>The current entity type builder.</returns>
    public EntityTypeBuilder<T> HasIndex<TKey>(Expression<Func<T, TKey>> keySelector, string? name = null,
        bool unique = false)
    {
        Indexes.Add(new IndexBuilder<T>(keySelector, name, unique));
        return this;
    }

    /// <summary>
    ///     Adds a vector index for the specified key selector.
    /// </summary>
    /// <typeparam name="TKey">The key type.</typeparam>
    /// <param name="keySelector">The key selector expression.</param>
    /// <param name="dimensions">The vector dimensions.</param>
    /// <param name="metric">The vector similarity metric.</param>
    /// <param name="name">The optional index name.</param>
    /// <returns>The current entity type builder.</returns>
    public EntityTypeBuilder<T> HasVectorIndex<TKey>(Expression<Func<T, TKey>> keySelector, int dimensions,
        VectorMetric metric = VectorMetric.Cosine, string? name = null)
    {
        Indexes.Add(new IndexBuilder<T>(keySelector, name, false, IndexType.Vector, dimensions, metric));
        return this;
    }

    /// <summary>
    ///     Adds a spatial index for the specified key selector.
    /// </summary>
    /// <typeparam name="TKey">The key type.</typeparam>
    /// <param name="keySelector">The key selector expression.</param>
    /// <param name="name">The optional index name.</param>
    /// <returns>The current entity type builder.</returns>
    public EntityTypeBuilder<T> HasSpatialIndex<TKey>(Expression<Func<T, TKey>> keySelector, string? name = null)
    {
        Indexes.Add(new IndexBuilder<T>(keySelector, name, false, IndexType.Spatial));
        return this;
    }

    /// <summary>
    ///     Sets the primary key selector for the entity type.
    /// </summary>
    /// <typeparam name="TKey">The key type.</typeparam>
    /// <param name="keySelector">The primary key selector expression.</param>
    /// <returns>The current entity type builder.</returns>
    public EntityTypeBuilder<T> HasKey<TKey>(Expression<Func<T, TKey>> keySelector)
    {
        PrimaryKeySelector = keySelector;
        PrimaryKeyName = ExpressionAnalyzer.ExtractPropertyPaths(keySelector).FirstOrDefault() ?? "_id";
        return this;
    }

    /// <summary>
    ///     Configures a converter for the primary key property.
    /// </summary>
    /// <typeparam name="TConverter">The converter type.</typeparam>
    /// <returns>The current entity type builder.</returns>
    public EntityTypeBuilder<T> HasConversion<TConverter>()
    {
        if (!string.IsNullOrEmpty(PrimaryKeyName)) PropertyConverters[PrimaryKeyName] = typeof(TConverter);
        return this;
    }

    /// <summary>
    ///     Configures a specific property on the entity type.
    /// </summary>
    /// <typeparam name="TProperty">The property type.</typeparam>
    /// <param name="propertyExpression">The property expression.</param>
    /// <returns>A builder for the selected property.</returns>
    public PropertyBuilder Property<TProperty>(Expression<Func<T, TProperty>> propertyExpression)
    {
        string? propertyName = ExpressionAnalyzer.ExtractPropertyPaths(propertyExpression).FirstOrDefault();
        return new PropertyBuilder(this, propertyName);
    }

    public class PropertyBuilder
    {
        private readonly EntityTypeBuilder<T> _parent;
        private readonly string? _propertyName;

        /// <summary>
        ///     Initializes a new instance of the <see cref="PropertyBuilder" /> class.
        /// </summary>
        /// <param name="parent">The parent entity type builder.</param>
        /// <param name="propertyName">The property name.</param>
        public PropertyBuilder(EntityTypeBuilder<T> parent, string? propertyName)
        {
            _parent = parent;
            _propertyName = propertyName;
        }

        /// <summary>
        ///     Marks the configured property as value generated on add.
        /// </summary>
        /// <returns>The current property builder.</returns>
        public PropertyBuilder ValueGeneratedOnAdd()
        {
            if (_propertyName == _parent.PrimaryKeyName) _parent.ValueGeneratedOnAdd = true;
            return this;
        }

        /// <summary>
        ///     Configures a converter for the configured property.
        /// </summary>
        /// <typeparam name="TConverter">The converter type.</typeparam>
        /// <returns>The current property builder.</returns>
        public PropertyBuilder HasConversion<TConverter>()
        {
            if (!string.IsNullOrEmpty(_propertyName)) _parent.PropertyConverters[_propertyName] = typeof(TConverter);
            return this;
        }
    }
}

public class IndexBuilder<T>
{
    /// <summary>
    ///     Initializes a new instance of the <see cref="IndexBuilder{T}" /> class.
    /// </summary>
    /// <param name="keySelector">The index key selector expression.</param>
    /// <param name="name">The optional index name.</param>
    /// <param name="unique">A value indicating whether the index is unique.</param>
    /// <param name="type">The index type.</param>
    /// <param name="dimensions">The vector dimensions.</param>
    /// <param name="metric">The vector metric.</param>
    public IndexBuilder(LambdaExpression keySelector, string? name, bool unique, IndexType type = IndexType.BTree,
        int dimensions = 0, VectorMetric metric = VectorMetric.Cosine)
    {
        KeySelector = keySelector;
        Name = name;
        IsUnique = unique;
        Type = type;
        Dimensions = dimensions;
        Metric = metric;
    }

    /// <summary>
    ///     Gets the index key selector expression.
    /// </summary>
    public LambdaExpression KeySelector { get; }

    /// <summary>
    ///     Gets the configured index name.
    /// </summary>
    public string? Name { get; }

    /// <summary>
    ///     Gets a value indicating whether the index is unique.
    /// </summary>
    public bool IsUnique { get; }

    /// <summary>
    ///     Gets the index type.
    /// </summary>
    public IndexType Type { get; }

    /// <summary>
    ///     Gets the vector dimensions.
    /// </summary>
    public int Dimensions { get; }

    /// <summary>
    ///     Gets the vector metric.
    /// </summary>
    public VectorMetric Metric { get; }
}