using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Filters;
using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
namespace Timeline.Controllers
{
    /// 
    /// Operations about timeline.
    /// 
    [ApiController]
    [Route("timelines")]
    [CatchTimelineNotExistException]
    [ProducesErrorResponseType(typeof(CommonResponse))]
    public class TimelineController : Controller
    {
        private readonly IUserService _userService;
        private readonly ITimelineService _service;
        private readonly TimelineMapper _timelineMapper;
        private readonly IMapper _mapper;
        /// 
        /// 
        /// 
        public TimelineController(IUserService userService, ITimelineService service, TimelineMapper timelineMapper, IMapper mapper)
        {
            _userService = userService;
            _service = service;
            _timelineMapper = timelineMapper;
            _mapper = mapper;
        }
        private bool UserHasAllTimelineManagementPermission => this.UserHasPermission(UserPermission.AllTimelineManagement);
        private Task Map(TimelineEntity timeline)
        {
            return _timelineMapper.MapToHttp(timeline, Url, this.GetOptionalUserId(), UserHasAllTimelineManagementPermission);
        }
        private Task> Map(List timelines)
        {
            return _timelineMapper.MapToHttp(timelines, Url, this.GetOptionalUserId(), UserHasAllTimelineManagementPermission);
        }
        /// 
        /// List all timelines.
        /// 
        /// A username. If set, only timelines related to the user will return.
        /// Specify the relation type, may be 'own' or 'join'. If not set, both type will return.
        /// "Private" or "Register" or "Public". If set, only timelines whose visibility is specified one will return.
        /// The timeline list.
        [HttpGet]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public async Task>> TimelineList([FromQuery][Username] string? relate, [FromQuery][ValidationSet("own", "join", "default")] string? relateType, [FromQuery] string? visibility)
        {
            List? visibilityFilter = null;
            if (visibility != null)
            {
                visibilityFilter = new List();
                var items = visibility.Split('|');
                foreach (var item in items)
                {
                    if (item.Equals(nameof(TimelineVisibility.Private), StringComparison.OrdinalIgnoreCase))
                    {
                        if (!visibilityFilter.Contains(TimelineVisibility.Private))
                            visibilityFilter.Add(TimelineVisibility.Private);
                    }
                    else if (item.Equals(nameof(TimelineVisibility.Register), StringComparison.OrdinalIgnoreCase))
                    {
                        if (!visibilityFilter.Contains(TimelineVisibility.Register))
                            visibilityFilter.Add(TimelineVisibility.Register);
                    }
                    else if (item.Equals(nameof(TimelineVisibility.Public), StringComparison.OrdinalIgnoreCase))
                    {
                        if (!visibilityFilter.Contains(TimelineVisibility.Public))
                            visibilityFilter.Add(TimelineVisibility.Public);
                    }
                    else
                    {
                        return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_QueryVisibilityUnknown, item));
                    }
                }
            }
            TimelineUserRelationship? relationship = null;
            if (relate != null)
            {
                try
                {
                    var relatedUserId = await _userService.GetUserIdByUsername(relate);
                    var relationType = relateType is null ? TimelineUserRelationshipType.Default : Enum.Parse(relateType, true);
                    relationship = new TimelineUserRelationship(relationType, relatedUserId);
                }
                catch (UserNotExistException)
                {
                    return BadRequest(ErrorResponse.TimelineController.QueryRelateNotExist());
                }
            }
            var timelines = await _service.GetTimelines(relationship, visibilityFilter);
            var result = await Map(timelines);
            return result;
        }
        /// 
        /// Get info of a timeline.
        /// 
        /// The timeline name.
        /// The timeline info.
        [HttpGet("{timeline}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        public async Task> TimelineGet([FromRoute][GeneralTimelineName] string timeline)
        {
            var timelineId = await _service.GetTimelineIdByName(timeline);
            var t = await _service.GetTimeline(timelineId);
            var result = await Map(t);
            return result;
        }
        /// 
        /// Change properties of a timeline.
        /// 
        /// Timeline name.
        /// 
        /// The new info.
        [HttpPatch("{timeline}")]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task> TimelinePatch([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePatchRequest body)
        {
            var timelineId = await _service.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermission(timelineId, this.GetUserId()))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            try
            {
                await _service.ChangeProperty(timelineId, _mapper.Map(body));
                var t = await _service.GetTimeline(timelineId);
                var result = await Map(t);
                return result;
            }
            catch (EntityAlreadyExistException)
            {
                return BadRequest(ErrorResponse.TimelineController.NameConflict());
            }
        }
        /// 
        /// Add a member to timeline.
        /// 
        /// Timeline name.
        /// The new member's username.
        [HttpPut("{timeline}/members/{member}")]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task> TimelineMemberPut([FromRoute][GeneralTimelineName] string timeline, [FromRoute][Username] string member)
        {
            var timelineId = await _service.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId())))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            try
            {
                var userId = await _userService.GetUserIdByUsername(member);
                var create = await _service.AddMember(timelineId, userId);
                return Ok(CommonPutResponse.Create(create));
            }
            catch (UserNotExistException)
            {
                return BadRequest(ErrorResponse.UserCommon.NotExist());
            }
        }
        /// 
        /// Remove a member from timeline.
        /// 
        /// Timeline name.
        /// The member's username.
        [HttpDelete("{timeline}/members/{member}")]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task TimelineMemberDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute][Username] string member)
        {
            var timelineId = await _service.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId())))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            try
            {
                var userId = await _userService.GetUserIdByUsername(member);
                var delete = await _service.RemoveMember(timelineId, userId);
                return Ok(CommonDeleteResponse.Create(delete));
            }
            catch (UserNotExistException)
            {
                return BadRequest(ErrorResponse.UserCommon.NotExist());
            }
        }
        /// 
        /// Create a timeline.
        /// 
        /// 
        /// Info of new timeline.
        [HttpPost]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        public async Task> TimelineCreate([FromBody] HttpTimelineCreateRequest body)
        {
            var userId = this.GetUserId();
            try
            {
                var timeline = await _service.CreateTimeline(body.Name, userId);
                var result = await Map(timeline);
                return result;
            }
            catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.Timeline)
            {
                return BadRequest(ErrorResponse.TimelineController.NameConflict());
            }
        }
        /// 
        /// Delete a timeline.
        /// 
        /// Timeline name.
        /// Info of deletion.
        [HttpDelete("{timeline}")]
        [Authorize]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
        public async Task TimelineDelete([FromRoute][TimelineName] string timeline)
        {
            var timelineId = await _service.GetTimelineIdByName(timeline);
            if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId())))
            {
                return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
            }
            try
            {
                await _service.DeleteTimeline(timelineId);
                return Ok();
            }
            catch (TimelineNotExistException)
            {
                return BadRequest(ErrorResponse.TimelineController.NotExist());
            }
        }
    }
}