using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using System;
using System.Threading.Tasks;
using Timeline.Filters;
using Timeline.Helpers;
using Timeline.Helpers.Cache;
using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
using static Timeline.Resources.Controllers.UserAvatarController;
namespace Timeline.Controllers
{
    /// 
    /// Operations about user avatar.
    /// 
    [ApiController]
    [ProducesErrorResponseType(typeof(CommonResponse))]
    public class UserAvatarController : Controller
    {
        private readonly ILogger _logger;
        private readonly IUserService _userService;
        private readonly IUserAvatarService _service;
        /// 
        /// 
        /// 
        public UserAvatarController(ILogger logger, IUserService userService, IUserAvatarService service)
        {
            _logger = logger;
            _userService = userService;
            _service = service;
        }
        /// 
        /// Get avatar of a user.
        /// 
        /// Username of the user to get avatar of.
        /// If-None-Match header.
        /// Avatar data.
        [HttpGet("users/{username}/avatar")]
        [Produces("image/png", "image/jpeg", "image/gif", "image/webp", "application/json", "text/json")]
        [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task Get([FromRoute][Username] string username, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch)
        {
            _ = ifNoneMatch;
            long id;
            try
            {
                id = await _userService.GetUserIdByUsername(username);
            }
            catch (UserNotExistException e)
            {
                _logger.LogInformation(e, Log.Format(LogGetUserNotExist, ("Username", username)));
                return NotFound(ErrorResponse.UserCommon.NotExist());
            }
            return await DataCacheHelper.GenerateActionResult(this, () => _service.GetAvatarDigest(id), () => _service.GetAvatar(id));
        }
        /// 
        /// Set avatar of a user. You have to be administrator to change other's.
        /// 
        /// Username of the user to set avatar of.
        /// The avatar data.
        [HttpPut("users/{username}/avatar")]
        [Authorize]
        [Consumes("image/png", "image/jpeg", "image/gif", "image/webp")]
        [MaxContentLength(1000 * 1000 * 10)]
        [ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task Put([FromRoute][Username] string username, [FromBody] ByteData body)
        {
            if (!this.UserHasPermission(UserPermission.UserManagement) && User.Identity!.Name != username)
            {
                _logger.LogInformation(Log.Format(LogPutForbid,
                    ("Operator Username", User.Identity.Name), ("Username To Put Avatar", username)));
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            long id;
            try
            {
                id = await _userService.GetUserIdByUsername(username);
            }
            catch (UserNotExistException e)
            {
                _logger.LogInformation(e, Log.Format(LogPutUserNotExist, ("Username", username)));
                return BadRequest(ErrorResponse.UserCommon.NotExist());
            }
            try
            {
                var digest = await _service.SetAvatar(id, body);
                _logger.LogInformation(Log.Format(LogPutSuccess,
                    ("Username", username), ("Mime Type", Request.ContentType)));
                Response.Headers.Append("ETag", $"\"{digest.ETag}\"");
                return Ok();
            }
            catch (ImageException e)
            {
                _logger.LogInformation(e, Log.Format(LogPutUserBadFormat, ("Username", username)));
                return BadRequest(e.Error switch
                {
                    ImageException.ErrorReason.CantDecode => ErrorResponse.UserAvatar.BadFormat_CantDecode(),
                    ImageException.ErrorReason.UnmatchedFormat => ErrorResponse.UserAvatar.BadFormat_UnmatchedFormat(),
                    ImageException.ErrorReason.NotSquare => ErrorResponse.UserAvatar.BadFormat_BadSize(),
                    _ =>
                        throw new Exception(ExceptionUnknownAvatarFormatError)
                });
            }
        }
        /// 
        /// Reset the avatar to the default one. You have to be administrator to reset other's.
        /// 
        /// Username of the user.
        /// Succeeded to reset.
        /// Error code is 10010001 if user does not exist.
        /// You have not logged in.
        /// You are not administrator.
        [HttpDelete("users/{username}/avatar")]
        [ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [Authorize]
        public async Task Delete([FromRoute][Username] string username)
        {
            if (!this.UserHasPermission(UserPermission.UserManagement) && User.Identity!.Name != username)
            {
                _logger.LogInformation(Log.Format(LogDeleteForbid,
                    ("Operator Username", User.Identity!.Name), ("Username To Delete Avatar", username)));
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            long id;
            try
            {
                id = await _userService.GetUserIdByUsername(username);
            }
            catch (UserNotExistException e)
            {
                _logger.LogInformation(e, Log.Format(LogDeleteNotExist, ("Username", username)));
                return BadRequest(ErrorResponse.UserCommon.NotExist());
            }
            await _service.DeleteAvatar(id);
            return Ok();
        }
    }
}