using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.S3Utils;
using InnovEnergy.Lib.S3Utils.DataTypes;
using InnovEnergy.Lib.Utils;
using SQLite;
using SQLiteConnection = SQLite.SQLiteConnection;


namespace InnovEnergy.App.Backend.Database;

public static partial class Db
{
    private static SQLiteConnection Connection { get; } = InitConnection();

    private static SQLiteConnection InitConnection()
    {
        var latestDb = new DirectoryInfo("DbBackups")
            .GetFiles()
            .OrderBy(f => f.LastWriteTime)
            .Last().Name;
        
        Console.WriteLine("latestdb is "+latestDb);

        //This is the file connection from the DbBackups folder
        var fileConnection = new SQLiteConnection("DbBackups/" + latestDb);

        //Create a table if it does not exist
        fileConnection.CreateTable<User>();
        fileConnection.CreateTable<Installation>();
        fileConnection.CreateTable<Folder>();
        fileConnection.CreateTable<FolderAccess>();
        fileConnection.CreateTable<InstallationAccess>();
        fileConnection.CreateTable<Session>();
        fileConnection.CreateTable<OrderNumber2Installation>();
        fileConnection.CreateTable<Error>();
        fileConnection.CreateTable<Warning>();
        fileConnection.CreateTable<UserAction>();

        return fileConnection;
        //return CopyDbToMemory(fileConnection);
    }

    private static SQLiteConnection CopyDbToMemory(SQLiteConnection fileConnection)
    {
        var memoryConnection = new SQLiteConnection(":memory:");

        //Create a table if it does not exist in main memory 
        memoryConnection.CreateTable<User>();
        memoryConnection.CreateTable<Installation>();
        memoryConnection.CreateTable<Folder>();
        memoryConnection.CreateTable<FolderAccess>();
        memoryConnection.CreateTable<InstallationAccess>();
        memoryConnection.CreateTable<Session>();
        memoryConnection.CreateTable<OrderNumber2Installation>();
        memoryConnection.CreateTable<Error>();
        memoryConnection.CreateTable<Warning>();
        fileConnection.CreateTable<UserAction>();

        //Copy all the existing tables from the disk to main memory
        fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
        fileConnection.Table<Folder>().ForEach(memoryConnection.Insert);
        fileConnection.Table<Installation>().ForEach(memoryConnection.Insert);
        fileConnection.Table<User>().ForEach(memoryConnection.Insert);
        fileConnection.Table<FolderAccess>().ForEach(memoryConnection.Insert);
        fileConnection.Table<InstallationAccess>().ForEach(memoryConnection.Insert);
        fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert);
        fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
        fileConnection.Table<Warning>().ForEach(memoryConnection.Insert);
        fileConnection.Table<UserAction>().ForEach(memoryConnection.Insert); 
        
        return memoryConnection;
    }

    public static void BackupDatabase()
    {
        var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
        Connection.Backup("DbBackups/" + filename);
    }

    public static TableQuery<Session> Sessions => Connection.Table<Session>();
    public static TableQuery<Folder> Folders => Connection.Table<Folder>();
    public static TableQuery<Installation> Installations => Connection.Table<Installation>();
    public static TableQuery<User> Users => Connection.Table<User>();
    public static TableQuery<FolderAccess> FolderAccess => Connection.Table<FolderAccess>();
    public static TableQuery<InstallationAccess> InstallationAccess => Connection.Table<InstallationAccess>();
    public static TableQuery<OrderNumber2Installation> OrderNumber2Installation => Connection.Table<OrderNumber2Installation>();
    public static TableQuery<Error> Errors => Connection.Table<Error>();
    public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
    public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
    
    public static void Init()
    {
        // used to force static constructor
        //Since this class is static, we call Init method from the Program.cs to initialize all the fields of the class
    }

    //This is the constructor of the class
    static Db()
    {
        Connection.RunInTransaction(() =>
        {
            Connection.CreateTable<User>();
            Connection.CreateTable<Installation>();
            Connection.CreateTable<Folder>();
            Connection.CreateTable<FolderAccess>();
            Connection.CreateTable<InstallationAccess>();
            Connection.CreateTable<Session>();
            Connection.CreateTable<OrderNumber2Installation>();
            Connection.CreateTable<Error>();
            Connection.CreateTable<Warning>();
            Connection.CreateTable<UserAction>();
        });
        
        //UpdateKeys();
        CleanupSessions().SupressAwaitWarning();
        
    }
    
    private static async Task CleanupSessions()
    {
        while (true)
        {
            try
            {
                DeleteStaleSessions();
            }
            catch(Exception e)
            {
                Console.WriteLine("An error has occured when cleaning stale sessions, exception is:\n"+e);
            }

            await Task.Delay(TimeSpan.FromHours(0.5));
        }
    }
    
    private static async Task RemoveNonExistingKeys()
    {
        while (true)
        {
            try
            {
                var validReadKeys = Installations
                    .Select(i => i.S3Key)
                    .Distinct()
                    .ToList();
                
                

                var validWriteKeys = Installations
                    .Select(i => i.S3WriteKey)
                    .Distinct()
                    .ToList();

                Console.WriteLine("VALID READ KEYS");
                for (int i = 0; i < validReadKeys.Count; i++)
                {
                    Console.WriteLine(validReadKeys[i]);
                }
                
                Console.WriteLine("VALID WRITE KEYS");

                for (int i = 0; i < validReadKeys.Count; i++)
                {
                    Console.WriteLine(validWriteKeys[i]);
                }
              

                const String provider = "exo.io";
                var S3keys = await ExoCmd.GetAccessKeys();

                foreach (var keyMetadata in S3keys)
                {
                    if (keyMetadata["key"].ToString()!="EXOa0b53cf10517307cec1bf00e" && !validReadKeys.Contains(keyMetadata["key"].ToString()) && !validWriteKeys.Contains(keyMetadata["key"].ToString()))
                    {
                        //await ExoCmd.RevokeReadKey(keyMetadata["key"].ToString());
                        Console.WriteLine("Deleted key "+keyMetadata["key"]);
                    }

                }
            }
            catch(Exception e)
            {
                Console.WriteLine("An error has occured when updating S3 keys, exception is:\n"+e);
            }

            await Task.Delay(TimeSpan.FromHours(24));
        }
    
    }

    private static async Task UpdateKeys()
    {
        while (true)
        {
            try
            {
                await UpdateS3Urls();
            }
            catch(Exception e)
            {
                Console.WriteLine("An error has occured when updating S3 keys, exception is:\n"+e);
            }

            await RemoveNonExistingKeys();

            await Task.Delay(TimeSpan.FromHours(24));
        }
    }



    private static Boolean RunTransaction(Func<Boolean> func)
    {
        var savepoint = Connection.SaveTransactionPoint();
        var success = false;

        try
        {
            success = func();
        }
        finally
        {
            if (success)
                Connection.Release(savepoint);
            else
                Connection.RollbackTo(savepoint);
        }

        return success;
    }
    

    private static void DeleteStaleSessions()
    {
        var deadline = DateTime.Now.AddDays((-1) * Session.MaxAge.Days);
        Sessions.Delete(s => s.LastSeen < deadline);
    }

    private static async Task UpdateS3Urls()
    {
        var regions = Installations
            .Select(i => i.S3Region)
            .Distinct()
            .ToList();

        const String provider = "exo.io";
        Console.WriteLine("-----------------------UPDATED READ KEYS-------------------------------------------------------------------");

        foreach (var region in regions)
        {
            var s3Region = new S3Region($"https://{region}.{provider}", ExoCmd.S3Credentials!);
            var bucketList = await s3Region.ListAllBuckets();

            var installations = from bucket in bucketList.Buckets
                from installation in Installations
                where installation.BucketName() == bucket.BucketName
                select installation;

            foreach (var installation in installations)
            {
                await installation.RenewS3Credentials();
            }

        }
    }

    public static async Task<Boolean> SendPasswordResetEmail(User user, String sessionToken)
    {
        try
        {
            await user.SendPasswordResetEmail(sessionToken);
            return true;
        }
        catch
        {
            return false;
        }
    }

    public static async Task<Boolean> SendNewUserEmail(User user)
    {
        try
        {
            await user.SendNewUserWelcomeMessage();
            return true;
        }
        catch
        {
            return false;
        }
    }

    public static Boolean DeleteUserPassword(User user)
    {
        user.Password = "";
        user.MustResetPassword = true;
        return Update(user);
    }
}