using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using InnovEnergy.App.Backend.Database;
using InnovEnergy.App.Backend.DataTypes;
using InnovEnergy.App.Backend.DataTypes.Methods;
using InnovEnergy.App.Backend.Relations;
using InnovEnergy.App.Backend.Websockets;
using InnovEnergy.Lib.Utils;
using Microsoft.AspNetCore.Mvc;

namespace InnovEnergy.App.Backend;

using Token = String;


[Controller]
[Route("api/")]
public class Controller : ControllerBase  
{
    
    [HttpPost(nameof(Login))]
    public ActionResult<Session> Login(String username, String? password)
    {
        var user = Db.GetUserByEmail(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.IsNullOrEmpty() && user.MustResetPassword) && !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(CreateWebSocket))]
    public async Task CreateWebSocket(Token authToken)
    {
        var session = Db.GetSession(authToken)?.User;

        if (session is null)
        {
            Console.WriteLine("------------------------------------Unauthorized user----------------------------------------------");
            HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            HttpContext.Abort(); 
            return;
        }

        if (!HttpContext.WebSockets.IsWebSocketRequest)
        {
            Console.WriteLine("------------------------------------Not a websocket request ----------------------------------------------");
            HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            HttpContext.Abort();
            return;
        }
       
        var webSocketContext = await HttpContext.WebSockets.AcceptWebSocketAsync();
        var webSocket = webSocketContext;
        
        //Handle the WebSocket connection
        await WebsocketManager.HandleWebSocketConnection(webSocket);
    }
    
    [HttpGet(nameof(GetAllErrorsForInstallation))]
    public ActionResult<IEnumerable<Error>> GetAllErrorsForInstallation(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 Db.Errors
            .Where(error => error.InstallationId == id)
            .OrderByDescending(error => error.Date)
            .ThenByDescending(error => error.Time)
            .ToList();
    }
    
    [HttpGet(nameof(GetAllWarningsForInstallation))]
    public ActionResult<IEnumerable<Warning>> GetAllWarningsForInstallation(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 Db.Warnings
            .Where(error => error.InstallationId == id)
            .OrderByDescending(error => error.Date)
            .ThenByDescending(error => error.Time)
            .ToList();
    }

    [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)
              .HideWriteKeyIfUserIsNotAdmin(user.HasWriteAccess);
    }
    
    [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).HideWriteKeyIfUserIsNotAdmin(user.HasWriteAccess))
               .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;
        
        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 %&@#!!!
        
                                // TODO Filter out write keys
        return new (foldersAndInstallations);
    }
    

    [HttpPost(nameof(CreateUser))]
    public async Task<ActionResult<User>> CreateUser([FromBody] User newUser, Token authToken)
    {
        
        var create = Db.GetSession(authToken).Create(newUser);
        if (create)
        {
            var mail_success= await Db.SendNewUserEmail(newUser);
            if (!mail_success)
            {
                Db.GetSession(authToken).Delete(newUser);
            }
            
            return mail_success ? newUser.HidePassword():Unauthorized();
        }

        return Unauthorized() ;
    }
    
    [HttpPost(nameof(CreateInstallation))]
    public async Task<ActionResult<Installation>> CreateInstallation([FromBody] Installation installation, Token authToken)
    {
        var session = Db.GetSession(authToken);
        
        if (! await session.Create(installation))
            return Unauthorized();
        
        return installation;
    }
    
    [HttpPost(nameof(CreateFolder))]
    public ActionResult<Folder> CreateFolder([FromBody] 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.GetInstallationById(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.GetInstallationById(installationAccess.InstallationId);
        var user         = Db.GetUserById(installationAccess.UserId);
        
        return session.RevokeUserAccessTo(user, installation) 
             ? Ok()
             : Unauthorized();
    }
    
    
    
    [HttpPut(nameof(UpdateUser))]
    public ActionResult<User> UpdateUser([FromBody]  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([FromBody] Installation installation, Token authToken)
    {
        var session = Db.GetSession(authToken);

        if (!session.Update(installation))
            return Unauthorized();
        
        return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(session!.User).HideWriteKeyIfUserIsNotAdmin(session.User.HasWriteAccess);
    }
    
    [HttpPost(nameof(AcknowledgeError))]
    public ActionResult AcknowledgeError(Int64 id, Token authToken)
    {
        var session = Db.GetSession(authToken);
        
        if (session == null)
            return Unauthorized();
        
        var error=Db.Errors
            .FirstOrDefault(error => error.Id == id);

        error.Seen = true;
        
        return Db.Update(error)
            ? Ok()
            : Unauthorized();
    }
    
    [HttpPost(nameof(AcknowledgeWarning))]
    public ActionResult AcknowledgeWarning(Int64 id, Token authToken)
    {
        var session = Db.GetSession(authToken);
        
        if (session == null)
            return Unauthorized();
        
        var warning=Db.Warnings
            .FirstOrDefault(warning => warning.Id == id);

        warning.Seen = true;
        
        return Db.Update(warning)
            ? Ok()
            : Unauthorized();
    }
    
    [HttpPut(nameof(UpdateFolder))]
    public ActionResult<Folder> UpdateFolder([FromBody] 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();
    }
    
        
    [HttpPost(nameof(EditInstallationConfig))]
    public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId, Token authToken)
    {
        var session = Db.GetSession(authToken);
        //Console.WriteLine(config.GridSetPoint);

        //var installationToUpdate = Db.GetInstallationById(installationId);
        
        return await session.SendInstallationConfig(installationId, config)
            ? 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 async Task<ActionResult> DeleteInstallation(Int64 installationId, Token authToken)
    {
        var session      = Db.GetSession(authToken);
        var installation = Db.GetInstallationById(installationId);

        return await 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();
        
    }
    
    [HttpPost(nameof(ResetPasswordRequest))]
    public async Task<ActionResult<IEnumerable<Object>>> ResetPasswordRequest(String username)
    {
        var user = Db.GetUserByEmail(username);
        
        if (user is null)
            return Unauthorized();
        
        var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
        var success = Db.Create(session);
        
        return success && await Db.SendPasswordResetEmail(user, session.Token)
             ? Ok() 
             : Unauthorized();
    }


    [HttpGet(nameof(ResetPassword))]
    public ActionResult<Object> ResetPassword(Token token)
    {
        var user = Db.GetSession(token)?.User;

        if (user is null)
            return Unauthorized();

        Db.DeleteUserPassword(user);

        return Redirect($"https://monitor.innov.energy/?username={user.Email}&reset=true"); // TODO: move to settings file
    }

}