using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Comparators;
using API.DTOs.ReadingLists;
using API.Entities;
using API.Extensions;
using API.Helpers;
using API.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
    public class ReadingListController : BaseApiController
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly ChapterSortComparerZeroFirst _chapterSortComparerForInChapterSorting = new ChapterSortComparerZeroFirst();
        public ReadingListController(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }
        /// 
        /// 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);
        }
        /// 
        /// 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(await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(userId, items.ToList()));
        }
        /// 
        /// Updates an items position
        /// 
        /// 
        /// 
        [HttpPost("update-position")]
        public async Task UpdateListItemPosition(UpdateReadingListPosition dto)
        {
            // Make sure UI buffers events
            var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(dto.ReadingListId)).ToList();
            var item = items.Find(r => r.Id == dto.ReadingListItemId);
            items.Remove(item);
            items.Insert(dto.ToPosition, item);
            for (var i = 0; i < items.Count; i++)
            {
                items[i].Order = i;
            }
            if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
            {
                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 readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
            readingList.Items = readingList.Items.Where(r => r.Id != dto.ReadingListItemId).ToList();
            var index = 0;
            foreach (var readingListItem in readingList.Items)
            {
                readingListItem.Order = index;
                index++;
            }
            if (!_unitOfWork.HasChanges()) return Ok();
            if (await _unitOfWork.CommitAsync())
            {
                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 userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
            var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId);
            items = await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(userId, items.ToList());
            // Collect all Ids to remove
            var itemIdsToRemove = items.Where(item => item.PagesRead == item.PagesTotal).Select(item => item.Id);
            try
            {
                var listItems =
                    (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).Where(r =>
                        itemIdsToRemove.Contains(r.Id));
                _unitOfWork.ReadingListRepository.BulkRemove(listItems);
                if (!_unitOfWork.HasChanges()) return Ok("Nothing to remove");
                await _unitOfWork.CommitAsync();
                return Ok("Updated");
            }
            catch
            {
                await _unitOfWork.RollbackAsync();
            }
            return BadRequest("Could not remove read items");
        }
        /// 
        /// Deletes a reading list
        /// 
        /// 
        /// 
        [HttpDelete]
        public async Task DeleteList([FromQuery] int readingListId)
        {
            var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
            var readingList = user.ReadingLists.SingleOrDefault(r => r.Id == readingListId);
            if (readingList == null)
            {
                return BadRequest("User is not associated with this reading list");
            }
            user.ReadingLists.Remove(readingList);
            if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
            {
                return Ok("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.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
            // 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");
            }
            user.ReadingLists.Add(new ReadingList()
            {
                Promoted = false,
                Title = dto.Title,
                Summary = string.Empty
            });
            if (!_unitOfWork.HasChanges()) return BadRequest("There was a problem creating list");
            await _unitOfWork.CommitAsync();
            return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByTitleAsync(dto.Title));
        }
        /// 
        /// Update the properites (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");
            if (!string.IsNullOrEmpty(dto.Title))
            {
                readingList.Title = dto.Title; // Should I check if this is unique?
            }
            if (!string.IsNullOrEmpty(dto.Title))
            {
                readingList.Summary = dto.Summary;
            }
            readingList.Promoted = dto.Promoted;
            _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 _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
            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 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 _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
            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 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 _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
            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 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 _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
            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 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 _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
            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 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");
        }
        /// 
        /// Adds a list of Chapters as reading list items to the passed reading list.
        /// 
        /// 
        /// 
        /// 
        /// True if new chapters were added
        private async Task AddChaptersToReadingList(int seriesId, IList chapterIds,
            ReadingList readingList)
        {
            readingList.Items ??= new List();
            var lastOrder = 0;
            if (readingList.Items.Any())
            {
                lastOrder = readingList.Items.DefaultIfEmpty().Max(rli => rli.Order);
            }
            var existingChapterExists = readingList.Items.Select(rli => rli.ChapterId).ToHashSet();
            var chaptersForSeries = (await _unitOfWork.ChapterRepository.GetChaptersByIdsAsync(chapterIds))
                .OrderBy(c => float.Parse(c.Volume.Name))
                .ThenBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting);
            var index = lastOrder + 1;
            foreach (var chapter in chaptersForSeries)
            {
                if (existingChapterExists.Contains(chapter.Id)) continue;
                readingList.Items.Add(new ReadingListItem()
                {
                    Order = index,
                    ChapterId = chapter.Id,
                    SeriesId = seriesId,
                    VolumeId = chapter.VolumeId
                });
                index += 1;
            }
            return index > lastOrder + 1;
        }
        /// 
        /// 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);
        }
    }
}