using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Constants;
using API.Data;
using API.Data.Repositories;
using API.DTOs.Collection;
using API.DTOs.CollectionTags;
using API.Entities;
using API.Extensions;
using API.Helpers.Builders;
using API.Services;
using API.Services.Plus;
using API.SignalR;
using Hangfire;
using Kavita.Common;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace API.Controllers;
#nullable enable
/// 
/// APIs for Collections
/// 
public class CollectionController : BaseApiController
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ICollectionTagService _collectionService;
    private readonly ILocalizationService _localizationService;
    private readonly IExternalMetadataService _externalMetadataService;
    private readonly ISmartCollectionSyncService _collectionSyncService;
    private readonly ILogger _logger;
    private readonly IEventHub _eventHub;
    /// 
    public CollectionController(IUnitOfWork unitOfWork, ICollectionTagService collectionService,
        ILocalizationService localizationService, IExternalMetadataService externalMetadataService,
        ISmartCollectionSyncService collectionSyncService, ILogger logger,
        IEventHub eventHub)
    {
        _unitOfWork = unitOfWork;
        _collectionService = collectionService;
        _localizationService = localizationService;
        _externalMetadataService = externalMetadataService;
        _collectionSyncService = collectionSyncService;
        _logger = logger;
        _eventHub = eventHub;
    }
    /// 
    /// Returns all Collection tags for a given User
    /// 
    /// 
    [HttpGet]
    public async Task>> GetAllTags(bool ownedOnly = false)
    {
        return Ok(await _unitOfWork.CollectionTagRepository.GetCollectionDtosAsync(User.GetUserId(), !ownedOnly));
    }
    /// 
    /// Returns a single Collection tag by Id for a given user
    /// 
    /// 
    /// 
    [HttpGet("single")]
    public async Task>> GetTag(int collectionId)
    {
        var collections = await _unitOfWork.CollectionTagRepository.GetCollectionDtosAsync(User.GetUserId(), false);
        return Ok(collections.FirstOrDefault(c => c.Id == collectionId));
    }
    /// 
    /// Returns all collections that contain the Series for the user with the option to allow for promoted collections (non-user owned)
    /// 
    /// 
    /// 
    /// 
    [HttpGet("all-series")]
    public async Task>> GetCollectionsBySeries(int seriesId, bool ownedOnly = false)
    {
        return Ok(await _unitOfWork.CollectionTagRepository.GetCollectionDtosBySeriesAsync(User.GetUserId(), seriesId, !ownedOnly));
    }
    /// 
    /// Checks if a collection exists with the name
    /// 
    /// If empty or null, will return true as that is invalid
    /// 
    [HttpGet("name-exists")]
    public async Task> DoesNameExists(string name)
    {
        return Ok(await _unitOfWork.CollectionTagRepository.CollectionExists(name, User.GetUserId()));
    }
    /// 
    /// Updates an existing tag with a new title, promotion status, and summary.
    /// UI does not contain controls to update title
    /// 
    /// 
    /// 
    [HttpPost("update")]
    public async Task UpdateTag(AppUserCollectionDto updatedTag)
    {
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        try
        {
            if (await _collectionService.UpdateTag(updatedTag, User.GetUserId()))
            {
                await _eventHub.SendMessageAsync(MessageFactory.CollectionUpdated,
                    MessageFactory.CollectionUpdatedEvent(updatedTag.Id), false);
                return Ok(await _localizationService.Translate(User.GetUserId(), "collection-updated-successfully"));
            }
        }
        catch (KavitaException ex)
        {
            return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
        }
        return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
    }
    /// 
    /// Promote/UnPromote multiple collections in one go. Will only update the authenticated user's collections and will only work if the user has promotion role
    /// 
    /// 
    /// 
    [HttpPost("promote-multiple")]
    public async Task PromoteMultipleCollections(PromoteCollectionsDto dto)
    {
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        // This needs to take into account owner as I can select other users cards
        var collections = await _unitOfWork.CollectionTagRepository.GetCollectionsByIds(dto.CollectionIds);
        var userId = User.GetUserId();
        if (!User.IsInRole(PolicyConstants.PromoteRole) && !User.IsInRole(PolicyConstants.AdminRole))
        {
            return BadRequest(await _localizationService.Translate(userId, "permission-denied"));
        }
        foreach (var collection in collections)
        {
            if (collection.AppUserId != userId) continue;
            collection.Promoted = dto.Promoted;
            _unitOfWork.CollectionTagRepository.Update(collection);
        }
        if (!_unitOfWork.HasChanges()) return Ok();
        await _unitOfWork.CommitAsync();
        return Ok();
    }
    /// 
    /// Delete multiple collections in one go
    /// 
    /// 
    /// 
    [HttpPost("delete-multiple")]
    public async Task DeleteMultipleCollections(DeleteCollectionsDto dto)
    {
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        // This needs to take into account owner as I can select other users cards
        var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
        if (user == null) return Unauthorized();
        user.Collections = user.Collections.Where(uc => !dto.CollectionIds.Contains(uc.Id)).ToList();
        _unitOfWork.UserRepository.Update(user);
        if (!_unitOfWork.HasChanges()) return Ok();
        await _unitOfWork.CommitAsync();
        return Ok();
    }
    /// 
    /// Adds multiple series to a collection. If tag id is 0, this will create a new tag.
    /// 
    /// 
    /// 
    [HttpPost("update-for-series")]
    public async Task AddToMultipleSeries(CollectionTagBulkAddDto dto)
    {
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        // Create a new tag and save
        var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
        if (user == null) return Unauthorized();
        AppUserCollection? tag;
        if (dto.CollectionTagId == 0)
        {
            tag = new AppUserCollectionBuilder(dto.CollectionTagTitle).Build();
            user.Collections.Add(tag);
        }
        else
        {
            // Validate tag doesn't exist
            tag = user.Collections.FirstOrDefault(t => t.Id == dto.CollectionTagId);
        }
        if (tag == null)
        {
            return BadRequest(_localizationService.Translate(User.GetUserId(), "collection-doesnt-exists"));
        }
        var series = await _unitOfWork.SeriesRepository.GetSeriesByIdsAsync(dto.SeriesIds.ToList(), false);
        foreach (var s in series)
        {
            if (tag.Items.Contains(s)) continue;
            tag.Items.Add(s);
        }
        _unitOfWork.UserRepository.Update(user);
        if (await _unitOfWork.CommitAsync()) return Ok();
        return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
    }
    /// 
    /// For a given tag, update the summary if summary has changed and remove a set of series from the tag.
    /// 
    /// 
    /// 
    [HttpPost("update-series")]
    public async Task RemoveTagFromMultipleSeries(UpdateSeriesForTagDto updateSeriesForTagDto)
    {
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        try
        {
            var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(updateSeriesForTagDto.Tag.Id, CollectionIncludes.Series);
            if (tag == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "collection-doesnt-exist"));
            if (await _collectionService.RemoveTagFromSeries(tag, updateSeriesForTagDto.SeriesIdsToRemove))
                return Ok(await _localizationService.Translate(User.GetUserId(), "collection-updated"));
        }
        catch (Exception)
        {
            await _unitOfWork.RollbackAsync();
        }
        return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
    }
    /// 
    /// Removes the collection tag from the user
    /// 
    /// 
    /// 
    [HttpDelete]
    public async Task DeleteTag(int tagId)
    {
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        try
        {
            var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
            if (user == null) return Unauthorized();
            if (user.Collections.All(c => c.Id != tagId))
                return BadRequest(await _localizationService.Translate(user.Id, "access-denied"));
            if (await _collectionService.DeleteTag(tagId, user))
            {
                return Ok(await _localizationService.Translate(User.GetUserId(), "collection-deleted"));
            }
        }
        catch (Exception ex)
        {
            await _unitOfWork.RollbackAsync();
        }
        return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
    }
    /// 
    /// For the authenticated user, if they have an active Kavita+ subscription and a MAL username on record,
    /// fetch their Mal interest stacks (including restacks)
    /// 
    /// 
    [HttpGet("mal-stacks")]
    public async Task>> GetMalStacksForUser()
    {
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        return Ok(await _externalMetadataService.GetStacksForUser(User.GetUserId()));
    }
    /// 
    /// Imports a MAL Stack into Kavita
    /// 
    /// 
    /// 
    [HttpPost("import-stack")]
    public async Task ImportMalStack(MalStackDto dto)
    {
        var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
        if (user == null) return Unauthorized();
        if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
        // Validation check to ensure stack doesn't exist already
        if (await _unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, user.Id))
        {
            return BadRequest(_localizationService.Translate(user.Id, "collection-already-exists"));
        }
        try
        {
            // Create new collection
            var newCollection = new AppUserCollectionBuilder(dto.Title)
                .WithSource(ScrobbleProvider.Mal)
                .WithSourceUrl(dto.Url)
                .Build();
            user.Collections.Add(newCollection);
            _unitOfWork.UserRepository.Update(user);
            await _unitOfWork.CommitAsync();
            // Trigger Stack Refresh for just one stack (not all)
            BackgroundJob.Enqueue(() => _collectionSyncService.Sync(newCollection.Id));
            return Ok();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "There was an issue importing MAL Stack");
        }
        return BadRequest(_localizationService.Translate(user.Id, "error-import-stack"));
    }
}