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.Validation;
using Timeline.Services;
using Timeline.Services.Mapper;
using Timeline.Services.Timeline;
using Timeline.Services.User;
namespace Timeline.Controllers
{
    /// 
    /// Operations about timeline.
    /// 
    [ApiController]
    [Route("timelines")]
    [ProducesErrorResponseType(typeof(CommonResponse))]
    public class TimelineController : MyControllerBase
    {
        private readonly IUserService _userService;
        private readonly ITimelineService _service;
        private readonly IGenericMapper _mapper;
        public TimelineController(IUserService userService, ITimelineService service, IGenericMapper mapper)
        {
            _userService = userService;
            _service = service;
            _mapper = mapper;
        }
        private bool UserHasAllTimelineManagementPermission => UserHasPermission(UserPermission.AllTimelineManagement);
        private Task Map(TimelineEntity timeline)
        {
            return _mapper.MapAsync(timeline, Url, User);
        }
        private Task> Map(List timelines)
        {
            return _mapper.MapListAsync(timelines, Url, User);
        }
        /// 
        /// 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 BadRequestWithCommonResponse(ErrorCodes.Common.InvalidModel, string.Format(Resource.MessageTimelineListQueryVisibilityUnknown, visibility));
                    }
                }
            }
            TimelineUserRelationship? relationship = null;
            if (relate != null)
            {
                try
                {
                    var relatedUserId = await _userService.GetUserIdByUsernameAsync(relate);
                    var relationType = relateType is null ? TimelineUserRelationshipType.Default : Enum.Parse(relateType, true);
                    relationship = new TimelineUserRelationship(relationType, relatedUserId);
                }
                catch (EntityNotExistException)
                {
                    return BadRequestWithCommonResponse(ErrorCodes.TimelineController.QueryRelateNotExist, Resource.MessageTimelineListQueryRelateNotExist);
                }
            }
            var timelines = await _service.GetTimelinesAsync(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.GetTimelineIdByNameAsync(timeline);
            var t = await _service.GetTimelineAsync(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.GetTimelineIdByNameAsync(timeline);
            if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermissionAsync(timelineId, GetUserId()))
            {
                return ForbidWithCommonResponse();
            }
            await _service.ChangePropertyAsync(timelineId, _mapper.AutoMapperMap(body));
            var t = await _service.GetTimelineAsync(timelineId);
            var result = await Map(t);
            return result;
        }
        /// 
        /// 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.GetTimelineIdByNameAsync(timeline);
            if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetUserId())))
            {
                return ForbidWithCommonResponse();
            }
            var userId = await _userService.GetUserIdByUsernameAsync(member);
            await _service.AddMemberAsync(timelineId, userId);
            return OkWithCommonResponse();
        }
        /// 
        /// Remove a member from timeline.
        /// 
        /// Timeline name.
        /// The member's username.
        [HttpDelete("{timeline}/members/{member}")]
        [Authorize]
        [NotEntityDelete]
        [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.GetTimelineIdByNameAsync(timeline);
            if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetUserId())))
            {
                return ForbidWithCommonResponse();
            }
            var userId = await _userService.GetUserIdByUsernameAsync(member);
            await _service.RemoveMemberAsync(timelineId, userId);
            return OkWithCommonResponse();
        }
        /// 
        /// 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 = GetUserId();
            var timeline = await _service.CreateTimelineAsync(body.Name, userId);
            var result = await Map(timeline);
            return result;
        }
        /// 
        /// 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)
        {
            try
            {
                var timelineId = await _service.GetTimelineIdByNameAsync(timeline);
                if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermissionAsync(timelineId, GetUserId())))
                {
                    return ForbidWithCommonResponse();
                }
                await _service.DeleteTimelineAsync(timelineId);
                return DeleteWithCommonDeleteResponse();
            }
            catch (EntityNotExistException)
            {
                if (UserHasAllTimelineManagementPermission)
                {
                    return DeleteWithCommonDeleteResponse(false);
                }
                else
                {
                    return ForbidWithCommonResponse();
                }
            }
        }
    }
}