using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces;
using Microsoft.Extensions.Logging;

namespace ErsatzTV.Scanner.Core.Emby;

public class EmbyCollectionScanner : IEmbyCollectionScanner
{
    private readonly IEmbyApiClient _embyApiClient;
    private readonly IScannerProxy _scannerProxy;
    private readonly IEmbyCollectionRepository _embyCollectionRepository;
    private readonly ILogger<EmbyCollectionScanner> _logger;

    public EmbyCollectionScanner(
        IScannerProxy scannerProxy,
        IEmbyCollectionRepository embyCollectionRepository,
        IEmbyApiClient embyApiClient,
        ILogger<EmbyCollectionScanner> logger)
    {
        _scannerProxy = scannerProxy;
        _embyCollectionRepository = embyCollectionRepository;
        _embyApiClient = embyApiClient;
        _logger = logger;
    }

    public async Task<Either<BaseError, Unit>> ScanCollections(string address, string apiKey, bool deepScan)
    {
        try
        {
            // need to call get libraries to find library that contains collections (box sets)
            await _embyApiClient.GetLibraries(address, apiKey);

            var incomingItemIds = new List<string>();

            // get all collections from db (item id, etag)
            List<EmbyCollection> existingCollections = await _embyCollectionRepository.GetCollections();

            await foreach ((EmbyCollection collection, int _) in _embyApiClient.GetCollectionLibraryItems(
                               address,
                               apiKey))
            {
                incomingItemIds.Add(collection.ItemId);

                Option<EmbyCollection> maybeExisting = existingCollections.Find(c => c.ItemId == collection.ItemId);

                // skip if unchanged (etag)
                if (!deepScan && await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty) ==
                    collection.Etag)
                {
                    _logger.LogDebug("Emby collection {Name} is unchanged", collection.Name);
                    continue;
                }

                // add if new
                if (maybeExisting.IsNone)
                {
                    _logger.LogDebug("Emby collection {Name} is new", collection.Name);
                    await _embyCollectionRepository.AddCollection(collection);
                }

                if (await SyncCollectionItems(address, apiKey, collection))
                {
                    // save collection etag
                    await _embyCollectionRepository.SetEtag(collection);
                }
            }

            // remove missing collections (and remove any lingering tags from those collections)
            foreach (EmbyCollection collection in existingCollections.Filter(e => !incomingItemIds.Contains(e.ItemId)))
            {
                await _embyCollectionRepository.RemoveCollection(collection);
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to get collections from Emby");
            return BaseError.New(ex.Message);
        }

        return Unit.Default;
    }

    private async Task<bool> SyncCollectionItems(string address, string apiKey, EmbyCollection collection)
    {
        try
        {
            // get collection items from Emby
            IAsyncEnumerable<Tuple<MediaItem, int>> items = _embyApiClient.GetCollectionItems(
                address,
                apiKey,
                collection.ItemId);

            List<int> removedIds = await _embyCollectionRepository.RemoveAllTags(collection);

            var movies = 0;
            var shows = 0;
            var seasons = 0;
            var episodes = 0;

            // sync tags on items
            var addedIds = new List<int>();
            await foreach ((MediaItem item, int _) in items)
            {
                Option<int> maybeId = await _embyCollectionRepository.AddTag(item, collection);
                foreach (int id in maybeId)
                {
                    addedIds.Add(id);

                    switch (item)
                    {
                        case Movie:
                            movies++;
                            break;
                        case Show:
                            shows++;
                            break;
                        case Season:
                            seasons++;
                            break;
                        case Episode:
                            episodes++;
                            break;
                    }
                }
            }

            if (addedIds.Count > 0)
            {
                _logger.LogDebug(
                    "Emby collection {Name} contains {Count} items ({Movies} movies, {Shows} shows, {Seasons} seasons, {Episodes} episodes)",
                    collection.Name,
                    addedIds.Count,
                    movies,
                    shows,
                    seasons,
                    episodes);
            }
            else
            {
                _logger.LogDebug("Emby collection {Name} contains no items that are also in ErsatzTV", collection.Name);
            }

            int[] changedIds = removedIds.Concat(addedIds).Distinct().ToArray();
            // if (changedIds.Length > 0)
            // {
            //     _logger.LogDebug("Reindexing ids {Ids}", changedIds.ToList());
            // }

            if (!await _scannerProxy.ReindexMediaItems(changedIds, CancellationToken.None))
            {
                _logger.LogWarning("Failed to reindex media items from scanner process");
                return false;
            }

            return true;
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to synchronize Emby collection {Name}", collection.Name);
            return false;
        }
    }
}
