using InABox.Clients;
using InABox.Core;
using InABox.Scripting;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InABox.Wpf.Dashboard;

public interface IDynamicDashboardTable
{
    string Key { get; set; }

    IEnumerable<CoreColumn> GetColumns(DynamicDashboardDataComponent data);
}

public interface IDynamicDashboardDataQuery : IDynamicDashboardTable
{
    Type Type { get; }

    IFilter? Filter { get; set; }

    IColumns Columns { get; set; }

    ISortOrder? SortOrder { get; set; }

    IEnumerable<CoreColumn> IDynamicDashboardTable.GetColumns(DynamicDashboardDataComponent data) =>
        Columns.Columns().Select(x => new CoreColumn
        {
            ColumnName = x.Name,
            DataType = x.Type
        });
}

public class DynamicDashboardDataQuery<T> : IDynamicDashboardDataQuery
    where T : Entity, IRemotable, new()
{
    public string Key { get; set; } = typeof(T).Name;

    public Filter<T>? Filter { get; set; }

    public Columns<T> Columns { get; set; } = Core.Columns.None<T>();

    public SortOrder<T>? SortOrder { get; set; } = null;

    #region IDynamicDashboardDataQuery

    Type IDynamicDashboardDataQuery.Type => typeof(T);

    IFilter? IDynamicDashboardDataQuery.Filter
    {
        get => Filter;
        set => Filter = value as Filter<T>;
    }

    IColumns IDynamicDashboardDataQuery.Columns
    {
        get => Columns;
        set => Columns = (value as Columns<T>) ?? Core.Columns.None<T>();
    }

    ISortOrder? IDynamicDashboardDataQuery.SortOrder
    {
        get => SortOrder;
        set => SortOrder = value as SortOrder<T>;
    }

    #endregion
}

public class DynamicDashboardAdditionalTable : IDynamicDashboardTable
{
    public string Key { get; set; } = "";

    private string? _script;
    public string? Script
    {
        get => _script;
        set
        {
            if(_script != value)
            {
                _script = value;
                _scriptDocument = null;
            }
        }
    }

    public IEnumerable<CoreColumn> GetColumns(DynamicDashboardDataComponent data) => SetupColumns(data);

    private ScriptDocument? _scriptDocument;
    private ScriptDocument? ScriptDocument
    {
        get
        {
            if(_scriptDocument is null && Script is not null)
            {
                _scriptDocument = new(Script);
                _scriptDocument.Compile();
            }
            return _scriptDocument;
        }
    }

    public static string DefaultScript()
    {
        return @"using InABox.Wpf.Dashboard;

public class Module
{
    public IEnumerable<CoreColumn> SetupColumns(DynamicDashboardDataComponent data)
    {
        // Return the list of columns for this table.
        return [
            new CoreColumn(typeof(string), ""Column1""),
            new CoreColumn(typeof(int), ""Column2"")];
    }

    public void PopulateTable(CoreTable table, DynamicDashboardData data)
    {
        // Populate 'table' using the data from 'data'.
        
        // For example, to add rows to the table:
        var newRow = table.NewRow();
        newRow[""Column1""] = ""First Column Data"";
        newRow[""Column2""] = 1;
        table.Rows.Add(newRow);
    }
}";
    }

    private IEnumerable<CoreColumn> SetupColumns(DynamicDashboardDataComponent data)
    {
        if (ScriptDocument is null) return [];

        var method = ScriptDocument.GetMethod(methodName: "SetupColumns");
        if(method is not null)
        {
            return method.Invoke(ScriptDocument!.GetObject(), [data]) as IEnumerable<CoreColumn>
                ?? [];
        }
        else
        {
            return [];
        }
    }

    public void PopulateTable(CoreTable table, DynamicDashboardData data)
    {
        if (ScriptDocument is null) return;

        ScriptDocument.Execute(methodname: "PopulateTable", parameters: [table, data]);
    }
}

public class DynamicDashboardDataComponent
{
    public List<IDynamicDashboardDataQuery> Queries { get; set; } = new();

    public List<DynamicDashboardAdditionalTable> AdditionalTables { get; set; } = new();

    public IEnumerable<IDynamicDashboardTable> Tables => Queries.Concat<IDynamicDashboardTable>(AdditionalTables);

    public IDynamicDashboardTable GetTable(string key)
    {
        return Tables.FirstOrDefault(x => x.Key == key)
            ?? throw new KeyNotFoundException($"Data table '{key}' does not exist.");
    }

    public bool TryGetTable(string key, [NotNullWhen(true)] out IDynamicDashboardTable? query)
    {
        query = Tables.FirstOrDefault(x => x.Key == key);
        return query != null;
    }

    private void PopulateAdditionalTables(DynamicDashboardData data)
    {
        foreach(var tableDef in AdditionalTables)
        {
            var table = new CoreTable();
            table.Columns.AddRange(tableDef.GetColumns(this));
            tableDef.PopulateTable(table, data);
            data.Data.Add(tableDef.Key, table);
        }
    }

    /// <summary>
    /// Run the query to get the data according to the definitions of <see cref="Queries"/>.
    /// </summary>
    /// <param name="maxRecords">Set to a non-<see langword="null"/> value to limit the number of records returned. Used for preview mode.</param>
    public DynamicDashboardData RunQuery(int? maxRecords = null)
    {
        var range = maxRecords.HasValue ? CoreRange.Database(maxRecords.Value) : null;
        var queryDefs = Queries.Select(x => new KeyedQueryDef(x.Key, x.Type, x.Filter, x.Columns, x.SortOrder, range));
        var results = Client.QueryMultiple(queryDefs);

        var data = new DynamicDashboardData(results.Results);
        PopulateAdditionalTables(data);
        return data;
    }

    /// <summary>
    /// Run the query asynchronously to get the data according to the definitions of <see cref="Queries"/>.
    /// </summary>
    /// <param name="maxRecords">Set to a non-<see langword="null"/> value to limit the number of records returned. Used for preview mode.</param>
    public async Task<DynamicDashboardData> RunQueryAsync(int? maxRecords = null)
    {
        var range = maxRecords.HasValue ? CoreRange.Database(maxRecords.Value) : null;
        var queryDefs = Queries.Select(x => new KeyedQueryDef(x.Key, x.Type, x.Filter, x.Columns, x.SortOrder, range));
        var results = await Client.QueryMultipleAsync(queryDefs);

        var data = new DynamicDashboardData(results.Results);

        PopulateAdditionalTables(data);

        return data;
    }
}

public class DynamicDashboardData(Dictionary<string, CoreTable> data)
{
    public Dictionary<string, CoreTable> Data { get; set; } = data;

    public bool TryGetData(string key, [NotNullWhen(true)] out CoreTable? query)
    {
        return Data.TryGetValue(key, out query);
    }
}
