using System.Diagnostics.CodeAnalysis;
using Innovenergy.Backend.Model;
using Innovenergy.Backend.Model.Relations;
using Innovenergy.Backend.Utils;
using InnovEnergy.Lib.Utils;
using SQLite;

namespace Innovenergy.Backend.Database;

public partial class Db : IDisposable
{
    internal const String DbPath = "./db.sqlite";
    
    private readonly SQLiteConnection _Db; // internal handle to the connection, disposable
    
    private TableQuery<Session> Sessions => _Db.Table<Session>();
    
    [SuppressMessage("ReSharper", "AccessToDisposedClosure")]
    static Db()
    {
        // on startup create/migrate tables
        
        using var db = new SQLiteConnection(DbPath);

        db.RunInTransaction(() =>
        {
            db.CreateTable<User>();
            db.CreateTable<Installation>();
            db.CreateTable<Folder>();
            db.CreateTable<User2Folder>();
            db.CreateTable<User2Installation>();
            db.CreateTable<Session>();
        });
        
    }

    // private, force access through Connect()
    private Db() => _Db = new SQLiteConnection(DbPath);
    
    public static Db Connect() => new Db();

    public void Dispose() => _Db.Dispose();
    
    
    // the C in CRUD
    private Result Create(TreeNode treeNode)
    {
        try
        {
            _Db.Insert(treeNode);
        }
        catch (Exception e)
        {
            return Result.Error(e);
        }

        return Result.Ok;
    }    
    
    // the U in CRUD
    private Result Update(TreeNode treeNode)
    {
        try
        {
            _Db.InsertOrReplace(treeNode);
        }
        catch (Exception e)
        {
            return Result.Error(e);
        }

        return Result.Ok;
    }
    
    // the D in CRUD
    private Result Delete(TreeNode treeNode)
    {
        try
        {
            _Db.Delete(treeNode);
        }
        catch (Exception e)
        {
            return Result.Error(e);
        }

        return Result.Ok;
    }
    
    public IEnumerable<Installation> GetAllAccessibleInstallations(User user)
    {
        var direct      = GetDirectlyAccessibleInstallations(user).ToList();
        var fromFolders = GetAllAccessibleFolders(user)
                         .SelectMany(GetChildInstallations)
                         .Except(direct);

        return direct.Concat(fromFolders);
    }
    

    
  
    public IEnumerable<Folder> GetAllAccessibleFolders(User user)
    {
        return GetDirectlyAccessibleFolders(user)
              .SelectMany(GetDescendantFolders);
    }
    
 
    public IEnumerable<Installation> GetDirectlyAccessibleInstallations(User user)
    {
        return User2Installation
              .Where(r => r.UserId == user.Id)
              .Select(r => r.InstallationId)
              .Select(GetInstallationById)
              .NotNull();
    }
    
    public IEnumerable<Folder> GetDirectlyAccessibleFolders(User user)
    {
        return User2Folder
              .Where(r => r.UserId == user.Id)
              .Select(r => r.FolderId)
              .Select(GetFolderById)
              .NotNull();
    }
    
    public Result AddToAccessibleInstallations(Int64 userId, Int64 updatedInstallationId)
    {
        var con = new User2Installation
        {
            UserId = userId,
            InstallationId = updatedInstallationId
        };

        try
        {
            _Db.InsertOrReplace(con);
        }
        catch (Exception e)
        {
            return Result.Error(e);
        }

        return Result.Ok;
    }
    
    public Result AddToAccessibleFolders(Int64 userId, Int64 updatedFolderId)
    {
        var con = new User2Folder
        {
            UserId = userId,
            FolderId = updatedFolderId
        };

        try
        {
            _Db.InsertOrReplace(con);
        }
        catch (Exception e)
        {
            return Result.Error(e);
        }

        return Result.Ok;
    }
    


    public User? GetUserByToken(String token)
    {
        return Sessions
              .Where(s => s.Token == token).ToList()
              .Where(s => s.Valid)
              .Select(s => s.UserId)
              .Select(GetUserById)
              .FirstOrDefault();
    }


    public Result NewSession(Session ses)
    {
        try
        {
            _Db.InsertOrReplace(ses);
        }
        catch (Exception e)
        {
            return Result.Error(e);
        }

        return Result.Ok;
    }

    public Result DeleteSession(Int64 id)
    {
        try
        {
            Sessions.Delete(u => u.UserId == id);
        }
        catch (Exception e)
        {
            return Result.Error(e);
        }

        return Result.Ok;
    }
    
}