using Coscine.Action;
using Coscine.Action.EventArgs;
using Coscine.ApiCommons;
using Coscine.Configuration;
using Coscine.Database.DataModel;
using Coscine.Database.Models;
using Coscine.Database.ReturnObjects;
using Coscine.Database.Util;
using Coscine.Logging;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Coscine.Api.Project.Controllers
{
    /// <summary>
    /// This represents the actions which can be taken with a ProjectRole Object
    /// </summary>
    [Authorize]
    public class ProjectRoleController : Controller
    {
        private readonly Authenticator _authenticator;
        private readonly ProjectRoleModel _projectRoleModel;
        private readonly Emitter _emitter;
        private readonly IConfiguration _configuration;
        private readonly CoscineLogger _coscineLogger;
        private readonly AnalyticsLogObject _analyticsLogObject;

        /// <summary>
        /// ProjectRoleController specifying an Emmiter, an Authentication,a ProjectRoleModel and a AnalyticsLogObject
        /// </summary>
        /// <param name="logger">Logger</param>
        public ProjectRoleController(ILogger<ProjectRoleController> logger)
        {
            _emitter = new Emitter(Program.Configuration);
            _authenticator = new Authenticator(this, Program.Configuration);
            _projectRoleModel = new ProjectRoleModel();
            _configuration = Program.Configuration;

            _coscineLogger = new CoscineLogger(logger);
            _analyticsLogObject = new AnalyticsLogObject();
        }

        /// <summary>
        /// Lists all users in a project.
        /// </summary>
        /// <param name="projectId">Id of the project</param>
        /// <returns>ProjectRoleObject or Json Object or Statuscode 401</returns>
        [Route("[controller]/{projectId}")]
        public ActionResult<IEnumerable<ProjectRoleObject>> Index(Guid projectId)
        {
            var userModel = new UserModel();
            var roleModel = new RoleModel();
            var projectModel = new ProjectModel();
            var user = _authenticator.GetUser();

            // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
            // - Project: View Members of Project on Project page
            if (projectModel.HasAccess(user, projectModel.GetById(projectId), UserRoles.Owner, UserRoles.Member, UserRoles.Guest))
            {
                var users = _projectRoleModel.GetAllWhere((projectRole) =>
                    (projectRole.ProjectId == projectId)
                ).Select((projectRole) =>
                {
                    var userInst = projectRole.User;
                    if (userInst == null)
                    {
                        userInst = userModel.GetById(projectRole.UserId);
                    }
                    var role = projectRole.Role;
                    if (role == null)
                    {
                        role = roleModel.GetById(projectRole.RoleId);
                    }
                    return new ProjectRoleObject(projectRole.ProjectId, new UserObject(userInst.Id, userInst.DisplayName, userInst.Givenname, userInst.Surname, userInst.EmailAddress), new RoleObject(role.Id, role.DisplayName));
                });
                if (Request.Query != null && Request.Query["noanalyticslog"] != "true")
                {
                    LogAnalytics("View Users", user, users, projectId.ToString());
                }

                return Ok(users);
            }
            else
            {
                return Unauthorized("User is not allowed to list all users to the given project!");
            }
        }

        /// <summary>
        /// Gets all roles for the current user and a project.
        /// </summary>
        /// <param name="projectId">Id of the project</param>
        /// <returns>Json Object or ProjectRoleObject</returns>
        [HttpGet("[controller]/project/{projectId}")]
        public ActionResult<IEnumerable<ProjectRoleObject>> Get(Guid projectId)
        {
            var roleModel = new RoleModel();
            var user = _authenticator.GetUser();
            var userObject = new UserObject(user.Id, user.DisplayName, user.Givenname, user.Surname, user.EmailAddress);

            return Ok(_projectRoleModel.GetAllWhere((projectRole) =>
                (projectRole.UserId == user.Id &&
                projectRole.ProjectId == projectId)
            ).Select((projectRole) =>
            {
                if (projectRole.Role == null)
                {
                    projectRole.Role = roleModel.GetById(projectRole.RoleId);
                }
                return new ProjectRoleObject(projectRole.RelationId, userObject, new RoleObject(projectRole.Role.Id, projectRole.Role.DisplayName));
            }));
        }

        /// <summary>
        /// Sets a role for the current user in a project
        /// </summary>
        /// <returns>Json Object or Status Code 401</returns>
        [HttpPost("[controller]")]
        public ActionResult<ProjectRoleObject> Set([FromBody] ProjectRoleObject projectRoleObject)
        {
            var projectModel = new ProjectModel();
            var project = projectModel.GetById(projectRoleObject.ProjectId);
            var roleModel = new RoleModel();
            var role = roleModel.GetById(projectRoleObject.Role.Id);
            var userModel = new UserModel();
            var userToAdd = userModel.GetById(projectRoleObject.User.Id);
            var user = _authenticator.GetUser();

            // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
            // - Project: Project: Change Settings (project, user, quota)
            if (projectModel.HasAccess(user, project, UserRoles.Owner))
            {
                // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
                // - none
                var userAlreadyHasRole = projectModel.HasAccess(userToAdd, project, UserRoles.Owner, UserRoles.Member, UserRoles.Guest);
                _emitter.EmitUserAdd(new UserEventArgs(_configuration)
                {
                    Project = project,
                    Role = role,
                    User = userToAdd
                });

                LogAnalytics(userAlreadyHasRole == true ? "Change Role" : "Add Member", user, null, project.Id.ToString());

                return Ok(_projectRoleModel.SetFromObject(projectRoleObject));
            }
            else
            {
                return Unauthorized("The user is not authorized to store a project role to the given project!");
            }
        }

        /// <summary>
        /// Deletes a user from a project
        /// </summary>
        /// <param name="projectId">Id of the project</param>
        /// <param name="userId">Id of the user</param>
        /// <param name="roleId">Id of the role</param>
        /// <returns>Json Object or Statuscode 401</returns>
        [HttpDelete("[controller]/project/{projectId}/user/{userId}/role/{roleId}")]
        public IActionResult Delete(Guid projectId, Guid userId, Guid roleId)
        {
            var projectModel = new ProjectModel();
            var user = _authenticator.GetUser();
            var project = projectModel.GetById(projectId);

            // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
            // - Project: Project: Change Settings (project, user, quota)
            if (projectModel.HasAccess(user, project, UserRoles.Owner))
            {
                _projectRoleModel.CheckIfLastOwnerWillBeRemoved(roleId, projectId);

                var userModel = new UserModel();
                var userToRemove = userModel.GetById(userId);

                _emitter.EmitUserRemove(new UserEventArgs(this._configuration)
                {
                    Project = project,
                    User = userToRemove
                });

                LogAnalytics("Remove User", user, null, project.Id.ToString());

                return Ok(_projectRoleModel.Delete(_projectRoleModel.GetWhere((projectRole) =>
                        projectRole.ProjectId == projectId
                        && projectRole.UserId == userId
                        && projectRole.RoleId == roleId)));
            }
            else
            {
                return Unauthorized("The user is not authorized to delete a project role for the given project!");
            }
        }

        /// <summary>
        /// Deletes the current user from a project
        /// </summary>
        /// <param name="projectId">Id of the project</param>
        /// <returns>Json Object or Statuscode 401</returns>
        [HttpDelete("[controller]/project/{projectId}/user")]
        public IActionResult Delete(Guid projectId)
        {
            var projectModel = new ProjectModel();
            var user = _authenticator.GetUser();
            var project = projectModel.GetById(projectId);
            var roleId = _projectRoleModel.GetGetUserRoleForProject(projectId, user.Id);

            // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md)
            // - ???
            // Guests have the right to leave a project on their own.
            if (projectModel.HasAccess(user, project, UserRoles.Owner, UserRoles.Member, UserRoles.Guest))
            {
                _projectRoleModel.CheckIfLastOwnerWillBeRemoved((Guid)roleId, projectId);
                _emitter.EmitUserRemove(new UserEventArgs(this._configuration)
                {
                    Project = project,
                    User = user
                });

                LogAnalytics("Leave Project", user, null, project.Id.ToString());
                return Ok(_projectRoleModel.Delete(_projectRoleModel.GetWhere((projectRole) =>
                        projectRole.ProjectId == projectId
                        && projectRole.UserId == user.Id
                        && projectRole.RoleId == roleId)));
            }
            else
            {
                return Unauthorized("The user is not authorized to access the given project!");
            }
        }

        /// <summary>
        /// Log Analytics
        /// </summary>
        /// <param name="operation">Operation</param>
        /// <param name="user">User object</param>
        /// <param name="users">Users</param>
        /// <param name="projectId">Id of the project</param>
        private void LogAnalytics(string operation,
            User user,
            IEnumerable<ProjectRoleObject> users = null,
            string projectId = null
        )
        {
            if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
            {
                _analyticsLogObject.Type = "Action";
                _analyticsLogObject.Operation = operation;

                if (users != null)
                {
                    var shownUsers = new List<string>();
                    foreach (var entry in users)
                    {
                        shownUsers.Add(entry.User.Id.ToString());
                    }
                    _analyticsLogObject.UserList = shownUsers;
                }

                if (projectId != null)
                {
                    _analyticsLogObject.ProjectId = projectId;
                    _analyticsLogObject.RoleId = _projectRoleModel.GetGetUserRoleForProject(new Guid(_analyticsLogObject.ProjectId), user.Id).ToString();
                }
                _coscineLogger.AnalyticsLog(_analyticsLogObject);
            }
        }
    }
}