using System.Data.SQLite;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using CliWrap;
using CliWrap.Buffered;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
using Microsoft.Identity.Client;
using SQLite;
using SQLiteConnection = SQLite.SQLiteConnection;


namespace InnovEnergy.App.Backend.Database;


public static partial class Db 
{
    // internal const String DbPath = "./db.sqlite";

    private static SQLiteConnection Connection { get; } = ((Func<SQLiteConnection>)(() =>
    {
        var latestDb = new DirectoryInfo(@"DbBackups").GetFiles()
            .OrderBy(f => f.LastWriteTime)
            .First().Name;

        var fileConnection = new SQLiteConnection("DbBackups/"+latestDb);
        
        var memoryConnection = new SQLiteConnection(":memory:");
        
        // fileConnection.Backup(memoryConnection.DatabasePath);
        
        memoryConnection.CreateTable<User>();
        memoryConnection.CreateTable<DeletedUser>();
        memoryConnection.CreateTable<Installation>();
        memoryConnection.CreateTable<DeletedInstallation>();
        memoryConnection.CreateTable<DeletedFolder>();
        memoryConnection.CreateTable<Folder>();
        memoryConnection.CreateTable<FolderAccess>();
        memoryConnection.CreateTable<InstallationAccess>();
        memoryConnection.CreateTable<Session>();
        memoryConnection.CreateTable<OrderNumber2Installation>();
        
        foreach (var obj in fileConnection.Table<Session>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<Folder>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<Installation>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<User>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<FolderAccess>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<InstallationAccess>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<OrderNumber2Installation>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<DeletedInstallation>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<DeletedUser>())
        {
            memoryConnection.Insert(obj);
        }
        foreach (var obj in fileConnection.Table<DeletedFolder>())
        {
            memoryConnection.Insert(obj);
        }
        
        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<DeletedInstallation>       DeletedInstallations     => Connection.Table<DeletedInstallation>();
    public static TableQuery<DeletedUser>               DeletedUsers             => Connection.Table<DeletedUser>();
    public static TableQuery<DeletedFolder>             DeletedFolders           => Connection.Table<DeletedFolder>();

    public static void Init()
    {
        // used to force static constructor
    }
    

    static Db()
    {
        // on startup create/migrate tables

        Connection.RunInTransaction(() =>
        {
            Connection.CreateTable<User>();
            Connection.CreateTable<DeletedUser>();
            Connection.CreateTable<Installation>();
            Connection.CreateTable<DeletedInstallation>();
            Connection.CreateTable<DeletedFolder>();
            Connection.CreateTable<Folder>();
            Connection.CreateTable<FolderAccess>();
            Connection.CreateTable<InstallationAccess>();
            Connection.CreateTable<Session>();
            Connection.CreateTable<OrderNumber2Installation>();
        });

        Observable.Interval(TimeSpan.FromDays(0.5))
                  .StartWith(0)         // Do it right away (on startup)      
                  .ObserveOn(TaskPoolScheduler.Default)
                  .SubscribeOn(TaskPoolScheduler.Default)
                  .SelectMany(Cleanup)
                  .Subscribe();
    }

    
    
    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 async Task<Boolean> Cleanup(Int64 _)
    {
        await UpdateS3Urls();
        DeleteStaleSessions();
        return true;
    }

    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 bucketList = await Cli.Wrap("exo")
            .WithArguments("storage list -O json")
            .ExecuteBufferedAsync();


        var installationsToUpdate = Installations
                .Select(i => i)
            .Where(i => bucketList.StandardOutput.Contains("\"" + i.BucketName())).ToList()
            ;
        
        foreach (var installation in installationsToUpdate)
        {
            await installation.RenewS3Credentials();
        }
    }
    
}