using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.Lib.Utils;
using Microsoft.AspNetCore.Mvc;

namespace InnovEnergy.App.Backend.Controllers;

using Token = String;

[ApiController]
[Route("api/")]
public class Controller : ControllerBase  
{
    [HttpPost(nameof(Login))]
    public ActionResult<Session> Login(String username, String password)
    {
        var user = Db.GetUserByName(username);

        if (user is null)
        {
            throw new Exceptions(400,"Null User Exception", "Must provide a user to log in as.", Request.Path.Value!);
        }

        if (!(user.Password is null && user.MustResetPassword))
        {
            if (!user.VerifyPassword(password))
            {
                //return Unauthorized("No Password set");
                throw new Exceptions(401,"Wrong Password Exception", "Please try again.", Request.Path.Value!);
            }
        }

        var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
        
        //TODO The Frontend should check for the MustResetPassword Flag

        return Db.Create(session) 
             ? session 
             : throw new Exceptions(401,"Session Creation Exception", "Not allowed to log in.", Request.Path.Value!);
    }


    [HttpPost(nameof(Logout))]
    public ActionResult Logout(Token authToken)
    {
        var session = Db.GetSession(authToken);

        return session.Logout()
             ? Ok()
             : Unauthorized();
    }
    

    [HttpGet(nameof(GetUserById))]
    public ActionResult<User> GetUserById(Int64 id, Token authToken)
    {
        var session = Db.GetSession(authToken)?.User;
        if (session == null)
            return Unauthorized();
    
        var user = Db.GetUserById(id);
    
        if (user is null || !session.HasAccessTo(user))
            return Unauthorized();

        return user.HidePassword().HideParentIfUserHasNoAccessToParent(session);
    }

    
    [HttpGet(nameof(GetInstallationById))]
    public ActionResult<Installation> GetInstallationById(Int64 id, Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();
    
        var installation = Db.GetInstallationById(id);
    
        if (installation is null || !user.HasAccessTo(installation))
            return Unauthorized();
    
        return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(user);
    }
    
    [HttpGet(nameof(GetUsersWithDirectAccessToInstallation))]
    public ActionResult<IEnumerable<Object>> GetUsersWithDirectAccessToInstallation(Int64 id, Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();
    
        var installation = Db.GetInstallationById(id);
        
        if (installation is null || !user.HasAccessTo(installation))
            return Unauthorized();

        return installation
            .UsersWithDirectAccess()
            .Where(u => u.IsDescendantOf(user))
            .Select(u => u.HidePassword())
            .ToList();
    }

    [HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
    public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();

        var installation = Db.GetInstallationById(id);

        if (installation is null || !user.HasAccessTo(installation))
            return Unauthorized();

        return installation
            .Ancestors()
            .SelectMany(f => f.UsersWithDirectAccess()
                .Where(u => u.IsDescendantOf(user))
                .Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() }))
            .ToList();
    }

    [HttpGet(nameof(GetUsersWithDirectAccessToFolder))]
    public ActionResult<IEnumerable<Object>> GetUsersWithDirectAccessToFolder(Int64 id, Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();
    
        var folder = Db.GetFolderById(id);
        
        if (folder is null || !user.HasAccessTo(folder))
            return Unauthorized();

        return folder
            .UsersWithDirectAccess()
            .Where(u => u.IsDescendantOf(user))
            .Select(u => u.HidePassword())
            .ToList();
    }
    
    [HttpGet(nameof(GetUsersWithInheritedAccessToFolder))]
    public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToFolder(Int64 id, Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();
    
        var folder = Db.GetFolderById(id);
        
        if (folder is null || !user.HasAccessTo(folder))
            return Unauthorized();

        return folder
            .Ancestors()
            .SelectMany(f => f.UsersWithDirectAccess()
                .Where(u => u.IsDescendantOf(user))
                .Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() }))
            .ToList();
    }
    
    [HttpGet(nameof(GetFolderById))]
    public ActionResult<Folder> GetFolderById(Int64 id, Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();
        
        var folder = Db.GetFolderById(id);
    
        if (folder is null || !user.HasAccessTo(folder))
            return Unauthorized();

        return folder.HideParentIfUserHasNoAccessToParent(user);
    }
    
    [HttpGet(nameof(GetAllDirectChildUsers))]
    public ActionResult<IEnumerable<User>> GetAllDirectChildUsers(Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();

        return user.ChildUsers().Select(u => u.HidePassword()).ToList();
    }

    [HttpGet(nameof(GetAllChildUsers))]
    public ActionResult<IEnumerable<User>> GetAllChildUsers(Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;
        if (user == null)
            return Unauthorized();

        return user.DescendantUsers().Select(u => u.HidePassword()).ToList();
    }


    [HttpGet(nameof(GetAllInstallations))]
    public ActionResult<IEnumerable<Installation>> GetAllInstallations(Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;

        if (user is null)
            return Unauthorized();

        return user
               .AccessibleInstallations()
               .Select(i => i.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(user))
               .ToList();
    }
    
    

    [HttpGet(nameof(GetAllFolders))]
    public ActionResult<IEnumerable<Folder>> GetAllFolders(Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;

        if (user is null)
            return Unauthorized();
        
        return new(user.AccessibleFolders().HideParentIfUserHasNoAccessToParent(user));
    }
    
    
    [HttpGet(nameof(GetAllFoldersAndInstallations))]
    public ActionResult<IEnumerable<Object>> GetAllFoldersAndInstallations(Token authToken)
    {
        var user = Db.GetSession(authToken)?.User;

        "GetAllFoldersAndInstallations".WriteLine();
        
        if (user is null)
            return Unauthorized();

        var foldersAndInstallations = user
            .AccessibleFoldersAndInstallations()
            .Do(o => o.FillOrderNumbers())
            .Select(o => o.HideParentIfUserHasNoAccessToParent(user))
            .OfType<Object>();  // Important! JSON serializer must see Objects otherwise
                                // it will just serialize the members of TreeNode %&@#!!!
        
        return new (foldersAndInstallations);
    }
    

    [HttpPost(nameof(CreateUser))]
    public ActionResult<User> CreateUser(User newUser, Token authToken)
    {
        return Db.GetSession(authToken).Create(newUser) 
             ? newUser.HidePassword() 
             : Unauthorized() ;
    }
    
    [HttpPost(nameof(CreateInstallation))]
    public async Task<ActionResult<Installation>> CreateInstallation(Installation installation, Token authToken)
    {
        var session = Db.GetSession(authToken);
        
        if (! await session.Create(installation))
            return Unauthorized();
        
        return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(session!.User);
    }
    
    [HttpPost(nameof(CreateFolder))]
    public ActionResult<Folder> CreateFolder(Folder folder, Token authToken)
    {
        var session = Db.GetSession(authToken);

        if (!session.Create(folder))
            return Unauthorized();
        
        return folder.HideParentIfUserHasNoAccessToParent(session!.User);
    }
    
    [HttpPost(nameof(GrantUserAccessToFolder))]
    public ActionResult GrantUserAccessToFolder(FolderAccess folderAccess, Token authToken)
    {
        var session = Db.GetSession(authToken);

        // TODO: automatic BadRequest when properties are null during deserialization
        var folder = Db.GetFolderById(folderAccess.FolderId);
        var user   = Db.GetUserById(folderAccess.UserId);

        return session.GrantUserAccessTo(user, folder) 
            ? Ok()
            : Unauthorized();
    }
    
    
    [HttpPost(nameof(RevokeUserAccessToFolder))]
    public ActionResult RevokeUserAccessToFolder(FolderAccess folderAccess, Token authToken)
    {
        var session = Db.GetSession(authToken);

        // TODO: automatic BadRequest when properties are null during deserialization
        var folder = Db.GetFolderById(folderAccess.FolderId);
        var user   = Db.GetUserById(folderAccess.UserId);

        return session.RevokeUserAccessTo(user, folder) 
             ? Ok()
             : Unauthorized();
    }
    
    
    [HttpPost(nameof(GrantUserAccessToInstallation))]
    public ActionResult GrantUserAccessToInstallation(InstallationAccess installationAccess, Token authToken)
    {
        var session = Db.GetSession(authToken);
       
        // TODO: automatic BadRequest when properties are null during deserialization
        var installation = Db.GetFolderById(installationAccess.InstallationId);
        var user         = Db.GetUserById(installationAccess.UserId);
        
        return session.GrantUserAccessTo(user, installation) 
            ? Ok()
            : Unauthorized();
    }
    
    [HttpPost(nameof(RevokeUserAccessToInstallation))]
    public ActionResult RevokeUserAccessToInstallation(InstallationAccess installationAccess, Token authToken)
    {
        var session = Db.GetSession(authToken);
       
        // TODO: automatic BadRequest when properties are null during deserialization
        var installation = Db.GetFolderById(installationAccess.InstallationId);
        var user         = Db.GetUserById(installationAccess.UserId);
        
        return session.RevokeUserAccessTo(user, installation) 
             ? Ok()
             : Unauthorized();
    }
    
    
    
    [HttpPut(nameof(UpdateUser))]
    public ActionResult<User> UpdateUser(User updatedUser, Token authToken)
    {
        var session = Db.GetSession(authToken);

        if (!session.Update(updatedUser)) 
            return Unauthorized();

        return updatedUser.HidePassword();
    }
    
    
    [HttpPut(nameof(UpdatePassword))]
    public ActionResult<User> UpdatePassword(String newPassword, Token authToken)
    {
        var session = Db.GetSession(authToken);

        return session.UpdatePassword(newPassword)
            ? Ok()
            : Unauthorized();
    }
    
    
    
    [HttpPut(nameof(UpdateInstallation))]
    public ActionResult<Installation> UpdateInstallation(Installation installation, Token authToken)
    {
        var session = Db.GetSession(authToken);

        if (!session.Update(installation))
            return Unauthorized();
        
        return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(session!.User);
    }
    
    
    [HttpPut(nameof(UpdateFolder))]
    public ActionResult<Folder> UpdateFolder(Folder folder, Token authToken)
    {
        var session = Db.GetSession(authToken);

        if (!session.Update(folder))
            return Unauthorized();
        
        return folder.HideParentIfUserHasNoAccessToParent(session!.User);
    }
    
    [HttpPut(nameof(MoveInstallation))]
    public ActionResult MoveInstallation(Int64 installationId,Int64 parentId, Token authToken)
    {
        var session = Db.GetSession(authToken);

        return session.MoveInstallation(installationId, parentId)
            ? Ok()
            : Unauthorized();
    }
    
    [HttpPut(nameof(MoveFolder))]
    public ActionResult MoveFolder(Int64 folderId,Int64 parentId, Token authToken)
    {
        var session = Db.GetSession(authToken);

        return session.MoveFolder(folderId, parentId)
            ? Ok()
            : Unauthorized();
    }
    
    [HttpDelete(nameof(DeleteUser))]
    public ActionResult DeleteUser(Int64 userId, Token authToken)
    {
        var session = Db.GetSession(authToken);
        var user    = Db.GetUserById(userId);

        return session.Delete(user) 
             ? Ok()
             : Unauthorized();
    }
    
    [HttpDelete(nameof(DeleteInstallation))]
    public ActionResult DeleteInstallation(Int64 installationId, Token authToken)
    {
        var session      = Db.GetSession(authToken);
        var installation = Db.GetInstallationById(installationId);

        return session.Delete(installation)
             ? Ok()  
             : Unauthorized();
    }

    [HttpDelete(nameof(DeleteFolder))]
    public ActionResult DeleteFolder(Int64 folderId, Token authToken)
    {
        var session = Db.GetSession(authToken);
        var folder  = Db.GetFolderById(folderId);

        return session.Delete(folder) 
             ? Ok() 
             : Unauthorized();
        
    }
}