using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Auth;
using Timeline.Filters;
using Timeline.Models.Http;
using Timeline.Models.Validation;
using Timeline.Services.Mapper;
using Timeline.Services.User;
namespace Timeline.Controllers
{
    /// 
    /// Operations about users.
    /// 
    [ApiController]
    [ProducesErrorResponseType(typeof(CommonResponse))]
    public class UserController : MyControllerBase
    {
        private readonly IUserService _userService;
        private readonly IUserPermissionService _userPermissionService;
        private readonly IUserDeleteService _userDeleteService;
        private readonly IGenericMapper _mapper;
        /// 
        public UserController(IUserService userService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, IGenericMapper mapper)
        {
            _userService = userService;
            _userPermissionService = userPermissionService;
            _userDeleteService = userDeleteService;
            _mapper = mapper;
        }
        private bool UserHasUserManagementPermission => UserHasPermission(UserPermission.UserManagement);
        /// 
        /// Get all users.
        /// 
        /// All user list.
        [HttpGet("users")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task>> List()
        {
            var users = await _userService.GetUsersAsync();
            var result = await _mapper.MapListAsync(users, Url, User);
            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)
        {
            var user = await _userService.CreateUserAsync(
                new CreateUserParams(body.Username, body.Password) { Nickname = body.Nickname });
            return await _mapper.MapAsync(user, Url, User);
        }
        /// 
        /// 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)
        {
            var id = await _userService.GetUserIdByUsernameAsync(username);
            var user = await _userService.GetUserAsync(id);
            return await _mapper.MapAsync(user, Url, User);
        }
        /// 
        /// 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)
            {
                var id = await _userService.GetUserIdByUsernameAsync(username);
                var user = await _userService.ModifyUserAsync(id, _mapper.AutoMapperMap(body));
                return await _mapper.MapAsync(user, Url, User);
            }
            else
            {
                if (GetUsername() != username)
                    return ForbidWithCommonResponse(Resource.MessageForbidNotAdministratorOrOwner);
                if (body.Username is not null)
                    return ForbidWithCommonResponse(Resource.MessageForbidNotAdministrator);
                if (body.Password is not null)
                    return ForbidWithCommonResponse(Resource.MessageForbidNotAdministrator);
                var user = await _userService.ModifyUserAsync(GetUserId(), _mapper.AutoMapperMap(body));
                return await _mapper.MapAsync(user, Url, User);
            }
        }
        /// 
        /// 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
            {
                await _userDeleteService.DeleteUserAsync(username);
                return DeleteWithCommonDeleteResponse();
            }
            catch (InvalidOperationOnRootUserException)
            {
                return BadRequestWithCommonResponse(ErrorCodes.UserController.InvalidOperationOnRootUser, Resource.MessageInvalidOperationOnRootUser);
            }
        }
        /// 
        /// 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 _userService.ChangePassword(GetUserId(), request.OldPassword, request.NewPassword);
                return OkWithCommonResponse();
            }
            catch (BadPasswordException)
            {
                return BadRequestWithCommonResponse(ErrorCodes.UserController.ChangePasswordBadOldPassword, Resource.MessageOldPasswordWrong);
            }
            // 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.GetUserIdByUsernameAsync(username);
                await _userPermissionService.AddPermissionToUserAsync(id, permission);
                return OkWithCommonResponse();
            }
            catch (InvalidOperationOnRootUserException)
            {
                return BadRequestWithCommonResponse(ErrorCodes.UserController.InvalidOperationOnRootUser, Resource.MessageInvalidOperationOnRootUser);
            }
        }
        [HttpDelete("users/{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
        [NotEntityDelete]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task> DeleteUserPermission([FromRoute][Username] string username, [FromRoute] UserPermission permission)
        {
            try
            {
                var id = await _userService.GetUserIdByUsernameAsync(username);
                await _userPermissionService.RemovePermissionFromUserAsync(id, permission);
                return OkWithCommonResponse();
            }
            catch (InvalidOperationOnRootUserException)
            {
                return BadRequestWithCommonResponse(ErrorCodes.UserController.InvalidOperationOnRootUser, Resource.MessageInvalidOperationOnRootUser);
            }
        }
    }
}