using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Timeline.Filters;
using Timeline.Helpers.Cache;
using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Entities;
namespace Timeline.Controllers
{
    /// 
    /// Operations about timeline.
    /// 
    [ApiController]
    [Route("timelines/{timeline}/posts")]
    [CatchTimelineNotExistException]
    [CatchTimelinePostNotExistException]
    [CatchTimelinePostDataNotExistException]
    [ProducesErrorResponseType(typeof(CommonResponse))]
    public class TimelinePostController : Controller
    {
        private readonly ITimelineService _timelineService;
        private readonly ITimelinePostService _postService;
        private readonly TimelineMapper _timelineMapper;
        private readonly MarkdownProcessor _markdownProcessor;
        /// 
        /// 
        /// 
        public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, TimelineMapper timelineMapper, MarkdownProcessor markdownProcessor)
        {
            _timelineService = timelineService;
            _postService = timelinePostService;
            _timelineMapper = timelineMapper;
            _markdownProcessor = markdownProcessor;
        }
        private bool UserHasAllTimelineManagementPermission => this.UserHasPermission(UserPermission.AllTimelineManagement);
        private Task Map(TimelinePostEntity post, string timelineName)
        {
            return _timelineMapper.MapToHttp(post, timelineName, Url, this.GetOptionalUserId(), UserHasAllTimelineManagementPermission);
        }
        private Task> Map(List posts, string timelineName)
        {
            return _timelineMapper.MapToHttp(posts, timelineName, Url, this.GetOptionalUserId(), UserHasAllTimelineManagementPermission);
        }
        /// 
        /// Get posts of a timeline.
        /// 
        /// The name of the timeline.
        /// If set, only posts modified since the time will return.
        /// If set to true, deleted post will also return.
        /// The post list.
        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task>> List([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted)
        {
            var timelineId = await _timelineService.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId()))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            var posts = await _postService.GetPosts(timelineId, modifiedSince, includeDeleted ?? false);
            var result = await Map(posts, timeline);
            return result;
        }
        /// 
        /// Get a post of a timeline.
        /// 
        /// The name of the timeline.
        /// The post id.
        /// The post.
        [HttpGet("{post}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task> Get([FromRoute][GeneralTimelineName] string timeline, [FromRoute(Name = "post")] long postId)
        {
            var timelineId = await _timelineService.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId()))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            var post = await _postService.GetPost(timelineId, postId);
            var result = await Map(post, timeline);
            return result;
        }
        /// 
        /// Get the first data of a post.
        /// 
        /// Timeline name.
        /// The id of the post.
        /// The data.
        [HttpGet("{post}/data")]
        [Produces(MimeTypes.ImagePng, MimeTypes.ImageJpeg, MimeTypes.ImageGif, MimeTypes.ImageWebp, MimeTypes.TextPlain, MimeTypes.TextMarkdown, MimeTypes.TextPlain, MimeTypes.ApplicationJson)]
        [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task> DataIndexGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post)
        {
            return await DataGet(timeline, post, 0);
        }
        /// 
        /// Get the data of a post. Usually a image post.
        /// 
        /// Timeline name.
        /// The id of the post.
        /// Index of the data.
        /// The data.
        [HttpGet("{post}/data/{data_index}")]
        [Produces(MimeTypes.ImagePng, MimeTypes.ImageJpeg, MimeTypes.ImageGif, MimeTypes.ImageWebp, MimeTypes.TextPlain, MimeTypes.TextMarkdown, MimeTypes.TextPlain, MimeTypes.ApplicationJson)]
        [ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task DataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromRoute(Name = "data_index")][Range(0, 100)] long dataIndex)
        {
            var timelineId = await _timelineService.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId()))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            return await DataCacheHelper.GenerateActionResult(this,
                () => _postService.GetPostDataDigest(timelineId, post, dataIndex),
                async () =>
                {
                    var data = await _postService.GetPostData(timelineId, post, dataIndex);
                    if (data.ContentType == MimeTypes.TextMarkdown)
                    {
                        return new ByteData(_markdownProcessor.Process(data.Data, Url, timeline, post), data.ContentType);
                    }
                    return data;
                }
            );
        }
        /// 
        /// Create a new post.
        /// 
        /// Timeline name.
        /// 
        /// Info of new post.
        [HttpPost]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task> Post([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body)
        {
            var timelineId = await _timelineService.GetTimelineIdByName(timeline);
            var userId = this.GetUserId();
            if (!UserHasAllTimelineManagementPermission && !await _timelineService.IsMemberOf(timelineId, userId))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            var createRequest = new TimelinePostCreateRequest()
            {
                Time = body.Time,
                Color = body.Color
            };
            for (int i = 0; i < body.DataList.Count; i++)
            {
                var data = body.DataList[i];
                if (data is null)
                    return BadRequest(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {i} is null."));
                try
                {
                    var d = Convert.FromBase64String(data.Data);
                    createRequest.DataList.Add(new TimelinePostCreateRequestData(data.ContentType, d));
                }
                catch (FormatException)
                {
                    return BadRequest(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {i} is not a valid base64 string."));
                }
            }
            try
            {
                var post = await _postService.CreatePost(timelineId, userId, createRequest);
                var result = await Map(post, timeline);
                return result;
            }
            catch (TimelinePostCreateDataException e)
            {
                return BadRequest(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {e.Index} is invalid. {e.Message}"));
            }
        }
        /// 
        /// Update a post except content.
        /// 
        /// Timeline name.
        /// Post id.
        /// Request body.
        /// New info of post.
        [HttpPatch("{post}")]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task> Patch([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromBody] HttpTimelinePostPatchRequest body)
        {
            var timelineId = await _timelineService.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(timelineId, post, this.GetUserId(), true))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            var entity = await _postService.PatchPost(timelineId, post, new TimelinePostPatchRequest { Time = body.Time, Color = body.Color });
            var result = await Map(entity, timeline);
            return Ok(result);
        }
        /// 
        /// Delete a post.
        /// 
        /// Timeline name.
        /// Post id.
        /// Info of deletion.
        [HttpDelete("{post}")]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task Delete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post)
        {
            var timelineId = await _timelineService.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(timelineId, post, this.GetUserId(), true))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            await _postService.DeletePost(timelineId, post);
            return Ok();
        }
    }
}