namespace CrupestApi.Commons.Crud;

public static class ColumnMetadataKeys
{
    public const string ColumnName = nameof(ColumnAttribute.ColumnName);
    public const string NotNull = nameof(ColumnAttribute.NotNull);
    public const string IsPrimaryKey = nameof(ColumnAttribute.IsPrimaryKey);
    public const string Index = nameof(ColumnAttribute.Index);

    /// <summary>
    /// This will add hooks for string type column to coerce null to ""(empty string) when get or set. No effect on non-string type.
    /// </summary> 
    public const string DefaultEmptyForString = nameof(ColumnAttribute.DefaultEmptyForString);

    /// <summary>
    /// This indicates that you take care of generate this column value when create entity. User calling the api can not specify the value.
    /// </summary>
    public const string OnlyGenerated = nameof(ColumnAttribute.OnlyGenerated);

    /// <summary>
    /// The default value generator method name in entity type. Default to null, aka, search for ColumnNameDefaultValueGenerator. 
    /// Generator has signature <code>static void DefaultValueGenerator(ColumnInfo column)</code>
    /// </summary>
    public const string DefaultValueGenerator = nameof(ColumnAttribute.DefaultValueGenerator);

    /// <summary>
    /// The validator method name in entity type. Default to null, aka, the default validator.
    /// Validator has signature <code>static void Validator(ColumnInfo column, object value)</code>
    /// Value param is never null. If you want to mean NULL, it should be a <see cref="DbNullValue"/>.
    /// </summary>
    public const string Validator = nameof(ColumnAttribute.Validator);

    /// <summary>
    /// The column can only be set when inserted, can't be changed in update.
    /// </summary>
    /// <returns></returns>
    public const string NoUpdate = nameof(ColumnAttribute.NoUpdate);

    /// <summary>
    /// This column acts as key when get one entity for http get method in path. 
    /// </summary>
    public const string ActAsKey = nameof(ColumnAttribute.ActAsKey);

    /// <summary>
    /// The default value used for the column.
    /// </summary>
    public const string DefaultValue = nameof(ColumnAttribute.DefaultValue);
}

public interface IColumnMetadata
{
    bool TryGetValue(string key, out object? value);

    object? GetValueOrDefault(string key)
    {
        if (TryGetValue(key, out var value))
        {
            return value;
        }
        else
        {
            return null;
        }
    }

    T? GetValueOrDefault<T>(string key)
    {
        return (T?)GetValueOrDefault(key);
    }

    object? this[string key]
    {
        get
        {
            if (TryGetValue(key, out var value))
            {
                return value;
            }
            else
            {
                throw new KeyNotFoundException("Key not found.");
            }
        }
    }
}

public enum ColumnIndexType
{
    None,
    Unique,
    NonUnique
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ColumnAttribute : Attribute, IColumnMetadata
{
    // if null, use the property name.
    public string? ColumnName { get; init; }

    // default false.
    public bool NotNull { get; init; }

    // default false
    public bool IsPrimaryKey { get; init; }

    // default None
    public ColumnIndexType Index { get; init; } = ColumnIndexType.None;

    /// <seealso cref="ColumnMetadataKeys.DefaultEmptyForString"/>
    public bool DefaultEmptyForString { get; init; }

    /// <seealso cref="ColumnMetadataKeys.OnlyGenerated"/>
    public bool OnlyGenerated { get; init; }

    /// <seealso cref="ColumnMetadataKeys.DefaultValueGenerator"/>
    public string? DefaultValueGenerator { get; init; }

    /// <seealso cref="ColumnMetadataKeys.Validator"/>
    public string? Validator { get; init; }

    /// <seealso cref="ColumnMetadataKeys.NoUpdate"/>
    public bool NoUpdate { get; init; }

    /// <seealso cref="ColumnMetadataKeys.ActAsKey"/>
    public bool ActAsKey { get; init; }

    public object? DefaultValue { get; init; }

    public bool TryGetValue(string key, out object? value)
    {
        var property = GetType().GetProperty(key);
        if (property is null)
        {
            value = null;
            return false;
        }
        value = property.GetValue(this);
        return true;
    }
}

public class AggregateColumnMetadata : IColumnMetadata
{
    private IDictionary<string, object?> _own = new Dictionary<string, object?>();
    private IList<IColumnMetadata> _children = new List<IColumnMetadata>();

    public void Add(string key, object? value)
    {
        _own[key] = value;
    }

    public void Remove(string key)
    {
        _own.Remove(key);
    }

    public void Add(IColumnMetadata child)
    {
        _children.Add(child);
    }

    public void Remove(IColumnMetadata child)
    {
        _children.Remove(child);
    }

    public bool TryGetValue(string key, out object? value)
    {
        if (_own.ContainsKey(key))
        {
            value = _own[key];
            return true;
        }

        bool found = false;
        value = null;
        foreach (var child in _children)
        {
            if (child.TryGetValue(key, out var tempValue))
            {
                value = tempValue;
                found = true;
            }
        }

        return found;
    }
}