using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using InABox.Core;
using InABox.Wpf;

namespace InABox.DynamicGrid;

public class FilterNode<T> : BaseFilterNode
{

    private Button ClearButton;
    private Button? LabelButton;
    private PropertyNode<T> Property;
    private OperatorNode Operator;
    private ValueNode Value;
    private Border ValuePlaceHolder;
    private ConstantNode? Constant;
    private ICustomValueNode? CustomValue;
    private Button AndButton;
    private Button OrButton;
    private FilterNodeGrid<T> Ands;
    private FilterNodeGrid<T> Ors;

    private FilterEditorConfiguration Configuration;

    private FilterNodeType filterType;

    public delegate void RemoveEventHandler();
    public event RemoveEventHandler? Remove;

    public FilterNode(Filter<T> filter, FilterNodeType filterType = FilterNodeType.ROOT, FilterEditorConfiguration? configuration = null)
    {
        Configuration = configuration ?? FilterEditorConfiguration.Default();

        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Clear
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Label
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Property
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Operator
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Constant
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // CustomValue
        ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); // Value
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // And
        ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // Or

        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto}); // Header
        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto}); // Ands
        RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto}); // Ors

        ClearButton = new Button { Content = "-", Width = 20, Margin = new Thickness(0, 0, 5, 0), Padding = new Thickness(5) };

        if (filterType != FilterNodeType.ROOT)
        {
            ClearButton.Click += (s, e) => Remove?.Invoke();

            LabelButton = new Button { Content = "-", Width = 50, Margin = new Thickness(0, 0, 5, 0), Padding = new Thickness(5) };

            if (filterType == FilterNodeType.AND)
                LabelButton.Content = "And";
            else if (filterType == FilterNodeType.OR)
                LabelButton.Content = "Or";
        }
        else
        {
            ClearButton.Click += ClearButton_Click;
        }

        this.filterType = filterType;

        Property = new(filter.Property) { Margin = new Thickness(0, 0, 5, 0) };
        Operator = new(filter.Operator, filter.IsNot) { Margin = new Thickness(0, 0, 5, 0) };

        ValuePlaceHolder = CreateValuePlaceHolder();
        
        var valueNode = Property.SelectedProperty != null ? CreateValueNode(Property.SelectedProperty, filter.Operator) : null;
        SetValueNode(valueNode ?? new EmptyValueNode(), filter.Value);

        Constant = CreateConstantNode(Property.SelectedProperty, filter.Operator, filter.Value as FilterConstant?);
        CheckValueVisibilities();

        CustomValue = CreateCustomValueNode(Property.SelectedProperty, filter.Operator, filter.Value as CustomFilterValue);
        CheckValueVisibilities();

        Ands = new FilterNodeGrid<T>(filter.Ands, FilterNodeType.AND, Configuration) { Margin = new Thickness(0, 0, 0, 0) };
        Ors = new FilterNodeGrid<T>(filter.Ors, FilterNodeType.OR, Configuration);

        AndButton = new Button { Content = "And", Width = 40, Margin = new Thickness(0, 0, 5, 0), Padding = new Thickness(5) };
        OrButton = new Button { Content = "Or", Width = 40, Margin = new Thickness(0, 0, 0, 0), Padding = new Thickness(5) };
        AndButton.Click += AndButton_Click;
        OrButton.Click += OrButton_Click;

        Property.PropertyChanged += Property_PropertyChanged;
        Operator.OperatorChanged += Operator_OperatorChanged;
        
        Add(ClearButton, 0, 0);
        if(LabelButton != null)
        {
            Add(LabelButton, 0, 1);
        }
        Add(Property, 0, 2);
        Add(Operator, 0, 3);
        if(Constant is not null)
        {
            Add(Constant, 0, 4);
        }
        if(CustomValue is not null)
        {
            Add(CustomValue.FrameworkElement, 0, 5);
        }
        Add(ValuePlaceHolder, 0, 6);
        Add(AndButton, 0, 7);
        Add(OrButton, 0, 8);

        Add(Ands, 1, 1, 8);
        Add(Ors, 2, 1, 8);
    }

    private void ClearButton_Click(object sender, RoutedEventArgs e)
    {
        Property.Clear();
        Operator.Clear();
        Ands.Clear();
        Ors.Clear();
    }

    private void AndButton_Click(object sender, RoutedEventArgs e)
    {
        Ands.AddFilter(new Filter<T>());
    }

    private void OrButton_Click(object sender, RoutedEventArgs e)
    {
        Ors.AddFilter(new Filter<T>());
    }

    public override Filter<T>? GetFilter()
    {
        var op = Operator.SelectedOperator;
        var isNot = Operator.IsNot;
        if (op == Core.Operator.All)
        {
            return isNot
                ? new Filter<T>().None()
                : new Filter<T>().All();
        }
        if (Operator.SelectedOperator == Core.Operator.None)
        {
            return isNot
                ? new Filter<T>().All()
                : new Filter<T>().None();
        }

        if (Property.SelectedProperty is null)
            return null;
        
        var filter = new Filter<T>();
        filter.Property = Property.SelectedProperty;
        filter.Operator = op;
        filter.IsNot = isNot;
        filter.Value = Constant?.SelectedConstant ?? CustomValue?.Value ?? Value.Value;

        foreach(var and in Ands.GetFilters())
        {
            filter.Ands.Add(and);
        }

        foreach(var or in Ors.GetFilters())
        {
            filter.Ors.Add(or);
        }

        return filter;
    }

    private Border CreateValuePlaceHolder()
    {
        Border result = new Border()
        {
            BorderThickness = new Thickness(0.75),
            BorderBrush = Brushes.Gray,
            Background = Brushes.WhiteSmoke,
            Padding = new Thickness(0),
            Margin = new Thickness(0,0,5,0)
        };
        return result;
    }

    private Type? ValueNodeType(Type propertyType)
    {
        if (propertyType == typeof(bool))
            return typeof(BooleanValueNode);
        if (propertyType == typeof(string))
            return typeof(StringValueNode);
        if (propertyType == typeof(Guid))
            return typeof(GuidValueNode);
        if (propertyType.IsOrdinal())
            return typeof(IntegerValueNode);
        if (propertyType == typeof(double))
            return typeof(DoubleValueNode);
        if (propertyType == typeof(DateTime))
            return typeof(DateTimeValueNode);
        if (propertyType == typeof(TimeSpan))
            return typeof(TimeSpanValueNode);
        if (propertyType.GetTypeInfo().IsEnum)
            return typeof(EnumValueNode<>).MakeGenericType(propertyType);
        return null;
    }

    private ValueNode? CreateValueNode(Type propertyType, Operator op)
    {
        switch (op)
        {
            case Core.Operator.InList:
            case Core.Operator.NotInList:
                var elementNodeType = ValueNodeType(propertyType);
                if (elementNodeType == null)
                    return null;
                return Activator.CreateInstance(typeof(ArrayValueNode<>).MakeGenericType(elementNodeType)) as ValueNode;

            case Core.Operator.BeginsWith:
            case Core.Operator.Contains:
            case Core.Operator.EndsWith:
            case Core.Operator.Regex:
                return new StringValueNode();

            case Core.Operator.InQuery:
                return new SubQueryNode(Configuration);

            case Core.Operator.NotInQuery:
                return new SubQueryNode(Configuration);

            case Core.Operator.All:
            case Core.Operator.None:
                return null;
        }
        var valueNodeType = ValueNodeType(propertyType);
        if (valueNodeType == null)
            return null;
        return Activator.CreateInstance(valueNodeType) as ValueNode;
    }

    private ValueNode? CreateValueNode(string propertyName, Operator op)
    {
        var property = DatabaseSchema.Property(typeof(T), propertyName);
        return property is not null ? CreateValueNode(property.PropertyType, op) : null;
    }

    [MemberNotNull(nameof(Value))]
    private void SetValueNode(ValueNode node, object? value)
    {
        Value = node;
        Value.Value = value;
        Value.Margin = new Thickness(0, 0, 5, 0);
        Add(Value, 0, 6);
        CheckValueVisibilities();
    }
        
    private ConstantNode? CreateConstantNode(string? propertyName, Operator op, FilterConstant? constantvalue)
    {
        var values = new List<KeyValuePair<string, FilterConstant?>>();
        if (!string.IsNullOrWhiteSpace(propertyName) && DatabaseSchema.Property(typeof(T), propertyName) is IProperty property)
        {
            var constants = FilterConstants.Constants(property.PropertyType, op, property.HasAttribute<AggregateAttribute>());
            if (constants.Length != 0)
            {
                values.Add(new KeyValuePair<string, FilterConstant?>("", null));
                foreach (var constant in constants)
                {
                    values.Add(new KeyValuePair<string, FilterConstant?>(CoreUtils.Neatify(constant.ToString()), constant));
                }
            }
        }

        if (!values.Any())
        {
            return null;
        }

        var result = new ConstantNode
        {
            DisplayMemberPath = "Key",
            SelectedValuePath = "Value",
            ItemsSource = values,
            SelectedConstant = constantvalue,
            Margin = new Thickness(0, 0, 5, 0)
        };
        result.ConstantChanged += ConstantChanged;
        return result;
    }

    private ICustomValueNode? CreateCustomValueNode(string? propertyName, Operator op, CustomFilterValue? value)
    {
        if (!Configuration.HasCustomValue)
        {
            return null;
        }
        var customValue = Configuration.CreateCustomValueNode(typeof(T), propertyName, op, value);
        if(customValue is not null)
        {
            customValue.FrameworkElement.Margin = new Thickness(0, 0, 5, 0);
            customValue.ValueChanged += CustomValue_ValueChanged;
        }
        return customValue;
    }

    private void CustomValue_ValueChanged(ICustomValueNode sender, CustomFilterValue? value)
    {
        CheckValueVisibilities();
    }

    private void Property_PropertyChanged(string? property)
    {
        if (Constant is not null)
        {
            Children.Remove(Constant);
        }
        Constant = CreateConstantNode(property, Operator.SelectedOperator, null);
        if(Constant is not null)
        {
            Add(Constant, 0, 4);
        }
            
        var value = Value.Value;
        Children.Remove(Value);
        var valueNode = property != null ?
            CreateValueNode(property, Operator.SelectedOperator) ?? new EmptyValueNode() :
            new EmptyValueNode();
        SetValueNode(
            valueNode,
            value
        );

        CustomFilterValue? customValue = null;
        if(CustomValue is not null)
        {
            customValue = CustomValue.Value;
            Children.Remove(CustomValue.FrameworkElement);
        }
        CustomValue = CreateCustomValueNode(property, Operator.SelectedOperator, customValue);
        if (CustomValue is not null)
        {
            Add(CustomValue.FrameworkElement, 0, 5);
        }
    }

    private void Operator_OperatorChanged(Operator op, bool isNot)
    {

        if (Constant is not null)
        {
            Children.Remove(Constant);
        }
        Constant = CreateConstantNode(Property.SelectedProperty, Operator.SelectedOperator, null);
        if (Constant is not null)
        {
            Add(Constant, 0, 4);
        }
        
        var value = Value.Value;
        Children.Remove(Value);

        ValueNode node;
        if (Property.SelectedProperty == null)
            node = new EmptyValueNode();
        else
            node = CreateValueNode(Property.SelectedProperty, op) ?? new EmptyValueNode();
        SetValueNode(
            node,
            value
        );
    }

    private void ConstantChanged(FilterConstant? constant)
    {
        CheckValueVisibilities();
    }

    private void CheckValueVisibilities()
    {
        if(Constant?.SelectedConstant != null)
        {
            Value.Visibility = Visibility.Collapsed;
            ValuePlaceHolder.Visibility = Visibility.Visible;
            Constant.Visibility = Visibility.Visible;
            if(CustomValue is not null)
            {
                CustomValue.FrameworkElement.Visibility = Visibility.Collapsed;
            }
        }
        else if(CustomValue?.Value != null)
        {
            Value.Visibility = Visibility.Collapsed;
            ValuePlaceHolder.Visibility = Visibility.Visible;
            if(Constant is not null)
            {
                Constant.Visibility = Visibility.Collapsed;
            }
            CustomValue.FrameworkElement.Visibility = Visibility.Visible;
        }
        else
        {
            Value.Visibility = Visibility.Visible;
            ValuePlaceHolder.Visibility = Visibility.Collapsed;
            if (Constant is not null)
            {
                Constant.Visibility = Visibility.Visible;
            }
            if (CustomValue is not null)
            {
                CustomValue.FrameworkElement.Visibility = Visibility.Visible;
            }
        }
    }

    private void Add(UIElement element, int row, int column = 1, int columnSpan = 1)
    {
        element.SetValue(RowProperty, row);
        element.SetValue(ColumnProperty, column);
        element.SetValue(ColumnSpanProperty, columnSpan);
        Children.Add(element);
    }
}