using Npgsql;
using NpgsqlTypes;
using System.Data;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using ServerDatabase.Utils;

namespace ServerDatabase;

public sealed class Database
{
    internal string ConectionString
    {
        get
        {
            return $"SSL Mode=Disable;Persist Security Info=True;Password={PW};Username={Uname};CommandTimeout={CMD};Database={DB};Host={IP}{CName}";
        }
    }

    public List<ITable> Tables { get; } = new()
    {
    };

    internal string? IP = null, DB = null, Uname = null, PW = null, CName = "";
    internal int CMD { get; set; } = 20;

    public Database(string IpAddress, string Database, string Username, string Password, string? CustomeName = null, int CommandTimeout = 20)
    {
		if (!string.IsNullOrWhiteSpace(IpAddress)) IP = IpAddress;
		if (!string.IsNullOrWhiteSpace(Database)) DB = Database;
		if (!string.IsNullOrWhiteSpace(Username)) Uname = Username;
		if (!string.IsNullOrWhiteSpace(Password)) PW = Password;
		if (!string.IsNullOrWhiteSpace(CustomeName)) CName = $";ApplicationName={CustomeName}";
		else CName = "";
		VersionsTable = new TableVersions(this);
        CMD = CommandTimeout;
	}

    public void RegisterTables()
    {
	    foreach (ITable Table in Tables)
	    {
		    foreach (ITableColumn Column in Table.Colums)
		    {
			    VersionsTable.WithColumn(new TableColumn<long>($"{Table.Name}_{Column.Name}")
			    {
				    DefaultValue = Column.ColumnVersion
			    }, string.Empty);
		    }
	    }
	    VersionsTable.UpdateTable();
	    foreach (ITable Table in Tables)
	    {
		    Table.UpdateTable();
	    }
    }

    public TableVersions VersionsTable { get; } = null!;

    public void CreateTable(string table, params ITableColumn[] Parameters)
	{
		if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined");
		using NpgsqlConnection con = new(ConectionString);
		con.Open();
		using NpgsqlCommand cmd = new();
		cmd.Connection = con;
		string command = $"CREATE TABLE public.{table}(";
		string key = "";
		foreach (ITableColumn column in Parameters)
		{
			command += $"{column.Name} {column.GetDatabaseTypeStr()}";
			if (column.IsPrimaryKey) key = $"PRIMARY KEY ({column.Name})";
			command += " NOT NULL, ";
		}
		if (key == "") command = command.Remove(command.Length - 2, 2) + ");";
		command += $"{key});";
		cmd.CommandText = command;
		cmd.Prepare();
		cmd.ExecuteNonQuery();
		con.Close();
	}
    
	public NpgsqlConnection CreateConnection()
    {
        if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined");
        return new NpgsqlConnection(ConectionString);
    }
	
	public void ExecuteNonQuery(string command)
	{
		if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined");
		using NpgsqlConnection connection = new(ConectionString);
		connection.Open();
		using NpgsqlCommand cmd = new();
		cmd.Connection = connection;
		cmd.CommandText = command;
		cmd.ExecuteNonQuery();
		connection.Close();
	}
	
	public T Read<T>(string command, Order? order = null, params Parameter[] Parameters)
	{
		if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined");
		using NpgsqlConnection con = new(ConectionString);
		con.Open();
		using NpgsqlCommand cmd = new();
		cmd.Connection = con;
		string vals = "";
		foreach (Parameter param in Parameters)
		{
			vals += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName} AND ";
			cmd.Parameters.Add(param.PGParameter);
		}
		if (command.EndsWith(";")) command = command.Remove(command.Length - 1, 1);
		if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5);
		if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}";
		if (order is not null) command += $" ORDER BY {order.Type} ";
		if (order is not null && order.Assending) command += "ASC";
		else { if (order is not null) command += "DESC"; }
		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!;
	}
}