using Npgsql;
using ServerDatabase.Utils;

namespace ServerDatabase;

public class CommandHandler<TClass> : ICommandHandler where TClass : class, new()
{
    internal List<IBetterTable> Tables = new();
    internal List<IBetterParameter> _Parameters = new();
    internal List<IBetterParameter> _Values = new();
    internal List<Parameter> _ValuesOld = new();
    internal List<Parameter> _ParametersOld = new();
    
    public IReadOnlyList<IBetterParameter> Parameters
    {
        get => _Parameters.AsReadOnly();
    }
    public IReadOnlyList<IBetterParameter> Values
    {
        get => _Values.AsReadOnly();
    }
    
    private Order? Order = null;
    private IBetterTable _betterTable;
    
    private string? ctc = null;
    private char table_as = 'A';
    private ICommandHandler? _CommandHandlerParent = null;
    private ICommandHandler? _Child = null;
    
    public char TableAsLetter { get => table_as; }
    public string? CTC { get => ctc; }
    public ICommandHandler? CommandHandlerParent { get => _CommandHandlerParent; }
    public ICommandHandler? Child { get => _Child; }
    public IBetterTable BetterTable { get => _betterTable; }
    
    internal CommandHandler(IBetterTable betterTable)
    {
        this._betterTable = betterTable;
    }
    
    private CommandHandler(IBetterTable betterTable, char TableAs, ICommandHandler ch)
    {
        this._betterTable = betterTable;
        this._CommandHandlerParent = ch;
        this.table_as = TableAs;
    }
    
    
    internal CommandHandler<TClass> WithFilter(IBetterParameter p)
    {
        _Parameters.Add(p);
        return this;
    }
    internal CommandHandler<TClass> WithValue(IBetterParameter p)
    {
        _Values.Add(p);
        return this;
    }
    
    internal CommandHandler<TClass> WithValue(Parameter p)
    {
        _ValuesOld.Add(p);
        return this;
    }
    public CommandHandler<TClass> WithFilter<T>(TableColumn<TClass, T> column, T value, string sign = "=") where T : notnull
    {
        return WithFilter(new BetterParameter<T>()
        {
            Column = column.Name,
            Sign = sign,
            Value = value,
        });
    }
    public CommandHandler<TClass> WithValue<T>(TableColumn<TClass, T> column, T value) where T : notnull
    {
        return WithValue(new BetterParameter<T>()
        {
            Column = column.Name,
            Sign = "=",
            Value = value,
        });
    }
    public CommandHandler<TClass> AscendBy<T>(TableColumn<TClass, T> column) where T : notnull
    {
        Order = column.GetAssendingOrder();
        return this;
    }
    
    public CommandHandler<TClass> DescendBy<T>(TableColumn<TClass, T> column) where T : notnull
    {
        Order = column.GetDecendingOrder();
        return this;
    }
    
    public CommandHandler<TCol> WithCrossTableCheck<TCol, T>(TableColumn<TClass, T> column1, Table<TCol> Table, TableColumn<TCol, T> column2) where TCol : class, new() where T : notnull
    {
        return WithCrossTableCheck(column1, "=", Table,column2);
    }

    public CommandHandler<TCol> WithCrossTableCheck<TCol, T>(TableColumn<TClass, T> column1, string sign, Table<TCol> Table, TableColumn<TCol, T> column2) where TCol : class, new() where T : notnull
    {
        ctc = $"{table_as}.{column1.Name} {sign} {(char)(table_as + 1)}.{column2.Name}";
        CommandHandler<TCol> child = new CommandHandler<TCol>(Table, (char)(table_as + 1), this);
        _Child = child;
        return child;
    }
    
    public T Read<T>(TableColumn<TClass, T> column) where T : notnull
    {
        string command = $"SELECT {TableAsLetter}.{column.Name} FROM {_betterTable.Name} AS {TableAsLetter}";
        ICommandHandler? pc = this;
        ICommandHandler? c = this.CommandHandlerParent;
        while (c is not null)
        {
            command += $", {c.BetterTable.Name} AS {c.TableAsLetter}";
            pc = c;
            c = c.CommandHandlerParent;
        }

        command += " WHERE";
        if (string.IsNullOrEmpty(_betterTable.DatabaseHandler.DB) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.IP) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.Uname) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.PW)) throw new Exception("Database conection not fully defined");
        using NpgsqlConnection con = new(_betterTable.DatabaseHandler.ConectionString);
        con.Open();
        using NpgsqlCommand cmd = new();
        cmd.Connection = con;

        char col_char = 'A';
        string vals = "";
        while (pc is not null)
        {
            foreach (IBetterParameter param in pc.Parameters)
            {
                vals += $"{pc.TableAsLetter}.{param.Column} {param.Sign} @{col_char} AND ";
                cmd.Parameters.Add(param.CreateParameter(col_char.ToString()));
                col_char++;
            }

            if (pc.CTC is not null) vals += pc.CTC + " AND ";
            pc = pc.Child;
        }
        
        if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5);
        if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}";
        command += ";";
        cmd.CommandText = command;
        object? temp = cmd.ExecuteScalar();
        con.Close();
        if (temp is DBNull || temp is null) return default!;
        if (typeof(T).IsEnum) return (T?)Enum.Parse(typeof(T), temp.ToString()!)!;
        if (typeof(T).IsNullableEnum()) return (T?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T))!, temp.ToString()!)!;
        return (T?)temp!;
    }
    
    public (T, T2) Read<T, T2>(TableColumn<TClass, T> column, TableColumn<TClass, T2> column2) where T : notnull where T2 : notnull
    {
        string command = $"SELECT {TableAsLetter}.{column.Name}, {TableAsLetter}.{column2.Name} FROM {_betterTable.Name} AS {TableAsLetter}";
        ICommandHandler? pc = this;
        ICommandHandler? c = this.CommandHandlerParent;
        while (c is not null)
        {
            command += $", {c.BetterTable.Name} AS {c.TableAsLetter}";
            pc = c;
            c = c.CommandHandlerParent;
        }

        command += " WHERE";
        if (string.IsNullOrEmpty(_betterTable.DatabaseHandler.DB) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.IP) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.Uname) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.PW)) throw new Exception("Database conection not fully defined");
        using NpgsqlConnection con = new(_betterTable.DatabaseHandler.ConectionString);
        con.Open();
        using NpgsqlCommand cmd = new();
        cmd.Connection = con;

        char col_char = 'A';
        string vals = "";
        while (pc is not null)
        {
            foreach (IBetterParameter param in pc.Parameters)
            {
                vals += $"{pc.TableAsLetter}.{param.Column} {param.Sign} @{col_char} AND ";
                cmd.Parameters.Add(param.CreateParameter(col_char.ToString()));
                col_char++;
            }

            if (pc.CTC is not null) vals += pc.CTC + " AND ";
            pc = pc.Child;
        }
        
        if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5);
        if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}";
        command += ";";
        cmd.CommandText = command;
        NpgsqlDataReader reader = cmd.ExecuteReader();
        reader.Read();
        object?[] temp = new object[reader.FieldCount];
        for (int i = 0; i < reader.FieldCount; i++)
        {
            string colname = reader.GetName(i);
            object val = reader.GetValue(i);
            if (i == 0)
            {
                if (typeof(T).IsEnum) temp[i] = (T?)Enum.Parse(typeof(T), val.ToString()!)!;
                if (typeof(T).IsNullableEnum()) temp[i] =  (T?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T))!, val.ToString()!)!;
            }
            else if (i == 1)
            {
                if (typeof(T2).IsEnum) temp[i] = (T2?)Enum.Parse(typeof(T2), val.ToString()!)!;
                if (typeof(T2).IsNullableEnum()) temp[i] =  (T2?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T2))!, val.ToString()!)!;
            }
            if (temp[i] is null) temp[i] = val;
        }
        con.Close();
        return ((T)temp[0]!, (T2)temp[1]!);
    }
    
    public (T, T2, T3) Read<T, T2, T3>(TableColumn<TClass, T> column, TableColumn<TClass, T2> column2, TableColumn<TClass, T3> column3) where T : notnull where T2 : notnull where T3 : notnull
    {
        string command = $"SELECT {TableAsLetter}.{column.Name}, {TableAsLetter}.{column2.Name}, {TableAsLetter}.{column3.Name} FROM {_betterTable.Name} AS {TableAsLetter}";
        ICommandHandler? pc = this;
        ICommandHandler? c = this.CommandHandlerParent;
        while (c is not null)
        {
            command += $", {c.BetterTable.Name} AS {c.TableAsLetter}";
            pc = c;
            c = c.CommandHandlerParent;
        }

        command += " WHERE";
        if (string.IsNullOrEmpty(_betterTable.DatabaseHandler.DB) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.IP) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.Uname) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.PW)) throw new Exception("Database conection not fully defined");
        using NpgsqlConnection con = new(_betterTable.DatabaseHandler.ConectionString);
        con.Open();
        using NpgsqlCommand cmd = new();
        cmd.Connection = con;

        char col_char = 'A';
        string vals = "";
        while (pc is not null)
        {
            foreach (IBetterParameter param in pc.Parameters)
            {
                vals += $"{pc.TableAsLetter}.{param.Column} {param.Sign} @{col_char} AND ";
                cmd.Parameters.Add(param.CreateParameter(col_char.ToString()));
                col_char++;
            }

            if (pc.CTC is not null) vals += pc.CTC + " AND ";
            pc = pc.Child;
        }
        
        if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5);
        if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}";
        command += ";";
        cmd.CommandText = command;
        NpgsqlDataReader reader = cmd.ExecuteReader();
        reader.Read();
        object?[] temp = new object[reader.FieldCount];
        for (int i = 0; i < reader.FieldCount; i++)
        {
            string colname = reader.GetName(i);
            object val = reader.GetValue(i);
            if (i == 0)
            {
                if (typeof(T).IsEnum) temp[i] = (T?)Enum.Parse(typeof(T), val.ToString()!)!;
                if (typeof(T).IsNullableEnum()) temp[i] =  (T?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T))!, val.ToString()!)!;
            }
            else if (i == 1)
            {
                if (typeof(T2).IsEnum) temp[i] = (T2?)Enum.Parse(typeof(T2), val.ToString()!)!;
                if (typeof(T2).IsNullableEnum()) temp[i] =  (T2?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T2))!, val.ToString()!)!;
            }
            else if (i == 2)
            {
                if (typeof(T3).IsEnum) temp[i] = (T3?)Enum.Parse(typeof(T3), val.ToString()!)!;
                if (typeof(T3).IsNullableEnum()) temp[i] =  (T3?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T3))!, val.ToString()!)!;
            }
            if (temp[i] is null) temp[i] = val;
        }
        con.Close();
        return ((T)temp[0]!, (T2)temp[1]!, (T3)temp[2]!);
    }
    
    public (T, T2, T3)[] ReadColumns<T, T2, T3>(TableColumn<TClass, T> column, TableColumn<TClass, T2> column2, TableColumn<TClass, T3> column3) where T : notnull where T2 : notnull where T3 : notnull
    {
        string command = $"SELECT {TableAsLetter}.{column.Name}, {TableAsLetter}.{column2.Name}, {TableAsLetter}.{column3.Name} FROM {_betterTable.Name} AS {TableAsLetter}";
        ICommandHandler? pc = this;
        ICommandHandler? c = this.CommandHandlerParent;
        while (c is not null)
        {
            command += $", {c.BetterTable.Name} AS {c.TableAsLetter}";
            pc = c;
            c = c.CommandHandlerParent;
        }

        command += " WHERE";
        if (string.IsNullOrEmpty(_betterTable.DatabaseHandler.DB) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.IP) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.Uname) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.PW)) throw new Exception("Database conection not fully defined");
        using NpgsqlConnection con = new(_betterTable.DatabaseHandler.ConectionString);
        con.Open();
        using NpgsqlCommand cmd = new();
        cmd.Connection = con;

        char col_char = 'A';
        string vals = "";
        while (pc is not null)
        {
            foreach (IBetterParameter param in pc.Parameters)
            {
                vals += $"{pc.TableAsLetter}.{param.Column} {param.Sign} @{col_char} AND ";
                cmd.Parameters.Add(param.CreateParameter(col_char.ToString()));
                col_char++;
            }

            if (pc.CTC is not null) vals += pc.CTC + " AND ";
            pc = pc.Child;
        }
        
        if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5);
        if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}";
        command += ";";
        cmd.CommandText = command;
        NpgsqlDataReader reader = cmd.ExecuteReader();
        List<(T, T2, T3)> l = new();
        while (reader.Read())
        {
            object?[] temp = new object[reader.FieldCount];
            for (int i = 0; i < reader.FieldCount; i++)
            {
                object val = reader.GetValue(i);
                if (i == 0)
                {
                    if (typeof(T).IsEnum) temp[i] = (T?)Enum.Parse(typeof(T), val.ToString()!)!;
                    if (typeof(T).IsNullableEnum()) temp[i] =  (T?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T))!, val.ToString()!)!;
                }
                else if (i == 1)
                {
                    if (typeof(T2).IsEnum) temp[i] = (T2?)Enum.Parse(typeof(T2), val.ToString()!)!;
                    if (typeof(T2).IsNullableEnum()) temp[i] =  (T2?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T2))!, val.ToString()!)!;
                }
                else if (i == 2)
                {
                    if (typeof(T3).IsEnum) temp[i] = (T3?)Enum.Parse(typeof(T3), val.ToString()!)!;
                    if (typeof(T3).IsNullableEnum()) temp[i] =  (T3?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T3))!, val.ToString()!)!;
                }
                if (temp[i] is null) temp[i] = val;
            }
            l.Add(new((T)temp[0]!, (T2)temp[1]!, (T3)temp[2]!));
        }
		
        con.Close();
        return l.ToArray();
    }
    
    
    public void Update()
    {
        string command = $"UPDATE {BetterTable.Name} AS {TableAsLetter} SET ";
        ICommandHandler? pc = this;
        ICommandHandler? c = this.CommandHandlerParent;
        using NpgsqlConnection con = new(_betterTable.DatabaseHandler.ConectionString);
        con.Open();
        using NpgsqlCommand cmd = new();
        cmd.Connection = con;
        string vals = "";
        char col_char = 'A';
        foreach (IBetterParameter param in Values)
        {
            vals += $"{param.Column} = @{col_char}, ";
            cmd.Parameters.Add(param.CreateParameter(col_char.ToString()));
            col_char++;
        }

        command += vals.Remove(vals.Length - 2, 2) + " ";
        string from = "";
        if (c is not null) from += "FROM ";
        while (c is not null)
        {
            from += $"{c.BetterTable.Name} AS {c.TableAsLetter}, ";
            pc = c;
            c = c.CommandHandlerParent;
        }

        if (from.Length > 0) command += from.Remove(from.Length - 2, 2);
        vals = "";
        while (pc is not null)
        {
            foreach (IBetterParameter param in pc.Parameters)
            {
                vals += $"{pc.TableAsLetter}.{param.Column} {param.Sign} @{col_char} AND ";
                cmd.Parameters.Add(param.CreateParameter(col_char.ToString()));
                col_char++;
            }

            if (pc.CTC is not null) vals += pc.CTC + " AND ";
            pc = pc.Child;
        }

        if (!string.IsNullOrWhiteSpace(vals))
        {
            vals = vals.Remove(vals.Length - 4, 4);
            command += "WHERE " + vals;
        }

        command = command.Remove(command.Length - 1, 1) + ";";
        cmd.CommandText = command;
        cmd.Prepare();
        cmd.ExecuteNonQuery();
        con.Close();
    }
}