using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Auth;
using Timeline.Helpers;
using Timeline.Models.Http;
using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
using static Timeline.Resources.Controllers.UserController;
using static Timeline.Resources.Messages;
namespace Timeline.Controllers
{
    /// 
    /// Operations about users.
    /// 
    [ApiController]
    [ProducesErrorResponseType(typeof(CommonResponse))]
    public class UserController : Controller
    {
        private readonly ILogger _logger;
        private readonly IUserService _userService;
        private readonly IUserCredentialService _userCredentialService;
        private readonly IUserPermissionService _userPermissionService;
        private readonly IUserDeleteService _userDeleteService;
        private readonly UserMapper _userMapper;
        private readonly IMapper _mapper;
        /// 
        public UserController(ILogger logger, IUserService userService, IUserCredentialService userCredentialService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, UserMapper userMapper, IMapper mapper)
        {
            _logger = logger;
            _userService = userService;
            _userCredentialService = userCredentialService;
            _userPermissionService = userPermissionService;
            _userDeleteService = userDeleteService;
            _userMapper = userMapper;
            _mapper = mapper;
        }
        private bool UserHasUserManagementPermission => this.UserHasPermission(UserPermission.UserManagement);
        /// 
        /// Get all users.
        /// 
        /// All user list.
        [HttpGet("users")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task>> List()
        {
            var users = await _userService.GetUsers();
            var result = await _userMapper.MapToHttp(users, Url);
            return result;
        }
        /// 
        /// Create a new user. You have to be administrator.
        /// 
        /// The new user's info.
        [HttpPost("users"), PermissionAuthorize(UserPermission.UserManagement)]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task> Post([FromBody] HttpUserPostRequest body)
        {
            try
            {
                var user = await _userService.CreateUser(body.Username, body.Password);
                return await _userMapper.MapToHttp(user, Url);
            }
            catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
            {
                return BadRequest(ErrorResponse.UserController.UsernameConflict());
            }
        }
        /// 
        /// Get a user's info.
        /// 
        /// Username of the user.
        /// User info.
        [HttpGet("users/{username}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task> Get([FromRoute][Username] string username)
        {
            try
            {
                var id = await _userService.GetUserIdByUsername(username);
                var user = await _userService.GetUser(id);
                return await _userMapper.MapToHttp(user, Url);
            }
            catch (UserNotExistException e)
            {
                _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
                return NotFound(ErrorResponse.UserCommon.NotExist());
            }
        }
        /// 
        /// Change a user's property.
        /// 
        /// 
        /// Username of the user to change.
        /// The new user info.
        [HttpPatch("users/{username}"), Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task> Patch([FromBody] HttpUserPatchRequest body, [FromRoute][Username] string username)
        {
            if (UserHasUserManagementPermission)
            {
                try
                {
                    var id = await _userService.GetUserIdByUsername(username);
                    var user = await _userService.ModifyUser(id, _mapper.Map(body));
                    return await _userMapper.MapToHttp(user, Url);
                }
                catch (UserNotExistException e)
                {
                    _logger.LogInformation(e, Log.Format(LogPatchUserNotExist, ("Username", username)));
                    return NotFound(ErrorResponse.UserCommon.NotExist());
                }
                catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
                {
                    return BadRequest(ErrorResponse.UserController.UsernameConflict());
                }
            }
            else
            {
                if (User.Identity!.Name != username)
                    return StatusCode(StatusCodes.Status403Forbidden,
                        ErrorResponse.Common.CustomMessage_Forbid(Common_Forbid_NotSelf));
                if (body.Username != null)
                    return StatusCode(StatusCodes.Status403Forbidden,
                        ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Username));
                if (body.Password != null)
                    return StatusCode(StatusCodes.Status403Forbidden,
                        ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password));
                var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map(body));
                return await _userMapper.MapToHttp(user, Url);
            }
        }
        /// 
        /// Delete a user and all his related data. You have to be administrator.
        /// 
        /// Username of the user to delete.
        /// Info of deletion.
        [HttpDelete("users/{username}"), PermissionAuthorize(UserPermission.UserManagement)]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task> Delete([FromRoute][Username] string username)
        {
            try
            {
                var delete = await _userDeleteService.DeleteUser(username);
                if (delete)
                    return Ok(CommonDeleteResponse.Delete());
                else
                    return Ok(CommonDeleteResponse.NotExist());
            }
            catch (InvalidOperationOnRootUserException)
            {
                return BadRequest(ErrorResponse.UserController.Delete_RootUser());
            }
        }
        /// 
        /// Change password with old password.
        /// 
        [HttpPost("userop/changepassword"), Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        public async Task ChangePassword([FromBody] HttpChangePasswordRequest request)
        {
            try
            {
                await _userCredentialService.ChangePassword(this.GetUserId(), request.OldPassword, request.NewPassword);
                return Ok();
            }
            catch (BadPasswordException e)
            {
                _logger.LogInformation(e, Log.Format(LogChangePasswordBadPassword,
                    ("Username", User.Identity!.Name), ("Old Password", request.OldPassword)));
                return BadRequest(ErrorResponse.UserController.ChangePassword_BadOldPassword());
            }
            // User can't be non-existent or the token is bad.
        }
        [HttpPut("users/{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task PutUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission)
        {
            try
            {
                var id = await _userService.GetUserIdByUsername(username);
                await _userPermissionService.AddPermissionToUserAsync(id, permission);
                return Ok();
            }
            catch (UserNotExistException)
            {
                return NotFound(ErrorResponse.UserCommon.NotExist());
            }
            catch (InvalidOperationOnRootUserException)
            {
                return BadRequest(ErrorResponse.UserController.ChangePermission_RootUser());
            }
        }
        [HttpDelete("users/{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task DeleteUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission)
        {
            try
            {
                var id = await _userService.GetUserIdByUsername(username);
                await _userPermissionService.RemovePermissionFromUserAsync(id, permission);
                return Ok();
            }
            catch (UserNotExistException)
            {
                return NotFound(ErrorResponse.UserCommon.NotExist());
            }
            catch (InvalidOperationOnRootUserException)
            {
                return BadRequest(ErrorResponse.UserController.ChangePermission_RootUser());
            }
        }
    }
}