using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Comparators;
using API.Data;
using API.Data.Repositories;
using API.DTOs.ReadingLists;
using API.Entities;
using API.Extensions;
using API.Helpers;
using API.Services;
using API.SignalR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
    [Authorize]
    public class ReadingListController : BaseApiController
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IEventHub _eventHub;
        private readonly IReadingListService _readingListService;
        private readonly ChapterSortComparerZeroFirst _chapterSortComparerForInChapterSorting = new ChapterSortComparerZeroFirst();
        public ReadingListController(IUnitOfWork unitOfWork, IEventHub eventHub, IReadingListService readingListService)
        {
            _unitOfWork = unitOfWork;
            _eventHub = eventHub;
            _readingListService = readingListService;
        }
        /// 
        /// Fetches a single Reading List
        /// 
        /// 
        /// 
        [HttpGet]
        public async Task>> GetList(int readingListId)
        {
            var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
            return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, userId));
        }
        /// 
        /// Returns reading lists (paginated) for a given user.
        /// 
        /// Defaults to true
        /// 
        [HttpPost("lists")]
        public async Task>> GetListsForUser([FromQuery] UserParams userParams, [FromQuery] bool includePromoted = true)
        {
            var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
            var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, includePromoted,
                userParams);
            Response.AddPaginationHeader(items.CurrentPage, items.PageSize, items.TotalCount, items.TotalPages);
            return Ok(items);
        }
        /// 
        /// Returns all Reading Lists the user has access to that have a series within it.
        /// 
        /// 
        /// 
        [HttpGet("lists-for-series")]
        public async Task>> GetListsForSeries(int seriesId)
        {
            var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
            var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForSeriesAndUserAsync(userId, seriesId, true);
            return Ok(items);
        }
        /// 
        /// Fetches all reading list items for a given list including rich metadata around series, volume, chapters, and progress
        /// 
        /// This call is expensive
        /// 
        /// 
        [HttpGet("items")]
        public async Task>> GetListForUser(int readingListId)
        {
            var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
            var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId);
            return Ok(items);
        }
        /// 
        /// Updates an items position
        /// 
        /// 
        /// 
        [HttpPost("update-position")]
        public async Task UpdateListItemPosition(UpdateReadingListPosition dto)
        {
            // Make sure UI buffers events
            var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            if (await _readingListService.UpdateReadingListItemPosition(dto)) return Ok("Updated");
            return BadRequest("Couldn't update position");
        }
        /// 
        /// Deletes a list item from the list. Will reorder all item positions afterwards
        /// 
        /// 
        /// 
        [HttpPost("delete-item")]
        public async Task DeleteListItem(UpdateReadingListPosition dto)
        {
            var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            if (await _readingListService.DeleteReadingListItem(dto))
            {
                return Ok("Updated");
            }
            return BadRequest("Couldn't delete item");
        }
        /// 
        /// Removes all entries that are fully read from the reading list
        /// 
        /// 
        /// 
        [HttpPost("remove-read")]
        public async Task DeleteReadFromList([FromQuery] int readingListId)
        {
            var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            if (await _readingListService.RemoveFullyReadItems(readingListId, user))
            {
                return Ok("Updated");
            }
            return BadRequest("Could not remove read items");
        }
        /// 
        /// Deletes a reading list
        /// 
        /// 
        /// 
        [HttpDelete]
        public async Task DeleteList([FromQuery] int readingListId)
        {
            var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            if (await _readingListService.DeleteReadingList(readingListId, user)) return Ok("List was deleted");
            return BadRequest("There was an issue deleting reading list");
        }
        /// 
        /// Creates a new List with a unique title. Returns the new ReadingList back
        /// 
        /// 
        /// 
        [HttpPost("create")]
        public async Task> CreateList(CreateReadingListDto dto)
        {
            var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.ReadingListsWithItems);
            // When creating, we need to make sure Title is unique
            var hasExisting = user.ReadingLists.Any(l => l.Title.Equals(dto.Title));
            if (hasExisting)
            {
                return BadRequest("A list of this name already exists");
            }
            var readingList = DbFactory.ReadingList(dto.Title, string.Empty, false);
            user.ReadingLists.Add(readingList);
            if (!_unitOfWork.HasChanges()) return BadRequest("There was a problem creating list");
            await _unitOfWork.CommitAsync();
            return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByTitleAsync(user.Id, dto.Title));
        }
        /// 
        /// Update the properties (title, summary) of a reading list
        /// 
        /// 
        /// 
        [HttpPost("update")]
        public async Task UpdateList(UpdateReadingListDto dto)
        {
            var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
            if (readingList == null) return BadRequest("List does not exist");
            var user = await _readingListService.UserHasReadingListAccess(readingList.Id, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            if (!string.IsNullOrEmpty(dto.Title))
            {
                readingList.Title = dto.Title; // Should I check if this is unique?
                readingList.NormalizedTitle = Services.Tasks.Scanner.Parser.Parser.Normalize(readingList.Title);
            }
            if (!string.IsNullOrEmpty(dto.Title))
            {
                readingList.Summary = dto.Summary;
            }
            readingList.Promoted = dto.Promoted;
            readingList.CoverImageLocked = dto.CoverImageLocked;
            if (!dto.CoverImageLocked)
            {
                readingList.CoverImageLocked = false;
                readingList.CoverImage = string.Empty;
                await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
                    MessageFactory.CoverUpdateEvent(readingList.Id, MessageFactoryEntityTypes.ReadingList), false);
                _unitOfWork.ReadingListRepository.Update(readingList);
            }
            _unitOfWork.ReadingListRepository.Update(readingList);
            if (await _unitOfWork.CommitAsync())
            {
                return Ok("Updated");
            }
            return BadRequest("Could not update reading list");
        }
        /// 
        /// Adds all chapters from a Series to a reading list
        /// 
        /// 
        /// 
        [HttpPost("update-by-series")]
        public async Task UpdateListBySeries(UpdateReadingListBySeriesDto dto)
        {
            var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
            if (readingList == null) return BadRequest("Reading List does not exist");
            var chapterIdsForSeries =
                await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new [] {dto.SeriesId});
            // If there are adds, tell tracking this has been modified
            if (await _readingListService.AddChaptersToReadingList(dto.SeriesId, chapterIdsForSeries, readingList))
            {
                _unitOfWork.ReadingListRepository.Update(readingList);
            }
            try
            {
                if (_unitOfWork.HasChanges())
                {
                    await _unitOfWork.CommitAsync();
                    return Ok("Updated");
                }
            }
            catch
            {
                await _unitOfWork.RollbackAsync();
            }
            return Ok("Nothing to do");
        }
        /// 
        /// Adds all chapters from a list of volumes and chapters to a reading list
        /// 
        /// 
        /// 
        [HttpPost("update-by-multiple")]
        public async Task UpdateListByMultiple(UpdateReadingListByMultipleDto dto)
        {
            var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
            if (readingList == null) return BadRequest("Reading List does not exist");
            var chapterIds = await _unitOfWork.VolumeRepository.GetChapterIdsByVolumeIds(dto.VolumeIds);
            foreach (var chapterId in dto.ChapterIds)
            {
                chapterIds.Add(chapterId);
            }
            // If there are adds, tell tracking this has been modified
            if (await _readingListService.AddChaptersToReadingList(dto.SeriesId, chapterIds, readingList))
            {
                _unitOfWork.ReadingListRepository.Update(readingList);
            }
            try
            {
                if (_unitOfWork.HasChanges())
                {
                    await _unitOfWork.CommitAsync();
                    return Ok("Updated");
                }
            }
            catch
            {
                await _unitOfWork.RollbackAsync();
            }
            return Ok("Nothing to do");
        }
        /// 
        /// Adds all chapters from a list of series to a reading list
        /// 
        /// 
        /// 
        [HttpPost("update-by-multiple-series")]
        public async Task UpdateListByMultipleSeries(UpdateReadingListByMultipleSeriesDto dto)
        {
            var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
            if (readingList == null) return BadRequest("Reading List does not exist");
            var ids = await _unitOfWork.SeriesRepository.GetChapterIdWithSeriesIdForSeriesAsync(dto.SeriesIds.ToArray());
            foreach (var seriesId in ids.Keys)
            {
                // If there are adds, tell tracking this has been modified
                if (await _readingListService.AddChaptersToReadingList(seriesId, ids[seriesId], readingList))
                {
                    _unitOfWork.ReadingListRepository.Update(readingList);
                }
            }
            try
            {
                if (_unitOfWork.HasChanges())
                {
                    await _unitOfWork.CommitAsync();
                    return Ok("Updated");
                }
            }
            catch
            {
                await _unitOfWork.RollbackAsync();
            }
            return Ok("Nothing to do");
        }
        [HttpPost("update-by-volume")]
        public async Task UpdateListByVolume(UpdateReadingListByVolumeDto dto)
        {
            var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
            if (readingList == null) return BadRequest("Reading List does not exist");
            var chapterIdsForVolume =
                (await _unitOfWork.ChapterRepository.GetChaptersAsync(dto.VolumeId)).Select(c => c.Id).ToList();
            // If there are adds, tell tracking this has been modified
            if (await _readingListService.AddChaptersToReadingList(dto.SeriesId, chapterIdsForVolume, readingList))
            {
                _unitOfWork.ReadingListRepository.Update(readingList);
            }
            try
            {
                if (_unitOfWork.HasChanges())
                {
                    await _unitOfWork.CommitAsync();
                    return Ok("Updated");
                }
            }
            catch
            {
                await _unitOfWork.RollbackAsync();
            }
            return Ok("Nothing to do");
        }
        [HttpPost("update-by-chapter")]
        public async Task UpdateListByChapter(UpdateReadingListByChapterDto dto)
        {
            var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
            if (user == null)
            {
                return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
            }
            var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
            if (readingList == null) return BadRequest("Reading List does not exist");
            // If there are adds, tell tracking this has been modified
            if (await _readingListService.AddChaptersToReadingList(dto.SeriesId, new List() { dto.ChapterId }, readingList))
            {
                _unitOfWork.ReadingListRepository.Update(readingList);
            }
            try
            {
                if (_unitOfWork.HasChanges())
                {
                    await _unitOfWork.CommitAsync();
                    return Ok("Updated");
                }
            }
            catch
            {
                await _unitOfWork.RollbackAsync();
            }
            return Ok("Nothing to do");
        }
        /// 
        /// Returns the next chapter within the reading list
        /// 
        /// 
        /// 
        /// Chapter Id for next item, -1 if nothing exists
        [HttpGet("next-chapter")]
        public async Task> GetNextChapter(int currentChapterId, int readingListId)
        {
            var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).ToList();
            var readingListItem = items.SingleOrDefault(rl => rl.ChapterId == currentChapterId);
            if (readingListItem == null) return BadRequest("Id does not exist");
            var index = items.IndexOf(readingListItem) + 1;
            if (items.Count > index)
            {
                return items[index].ChapterId;
            }
            return Ok(-1);
        }
        /// 
        /// Returns the prev chapter within the reading list
        /// 
        /// 
        /// 
        /// Chapter Id for next item, -1 if nothing exists
        [HttpGet("prev-chapter")]
        public async Task> GetPrevChapter(int currentChapterId, int readingListId)
        {
            var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).ToList();
            var readingListItem = items.SingleOrDefault(rl => rl.ChapterId == currentChapterId);
            if (readingListItem == null) return BadRequest("Id does not exist");
            var index = items.IndexOf(readingListItem) - 1;
            if (0 <= index)
            {
                return items[index].ChapterId;
            }
            return Ok(-1);
        }
    }
}