using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Olive.Commands;
using Olive.Helpers;
using Olive.Klipy;
using Olive.Services;
using Windows.Foundation;
using Windows.System;

namespace Olive.Pages;

internal sealed partial class GifPickerPage : IDynamicListPage
{
    private const int PageSize = 30;
    private const int InitialBatchPages = 1;
    private const int LoadMoreBatchPages = 1;
    private static readonly TimeSpan SearchDebounceDelay = TimeSpan.FromMilliseconds(500);

    private readonly Lock _itemsLock = new();
    private readonly GifCache _cache = new();
    private readonly SettingsManager _settingsManager;
    private readonly List<IListItem> _items = [];
    private readonly IIconInfo _icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.png");
    private readonly IGridProperties _gridProperties = new GalleryGridLayout { ShowTitle = true, ShowSubtitle = false };
    private CancellationTokenSource? _searchCancellation;
    private string _searchText = string.Empty;
    private string _activeSearch = string.Empty;
    private string _title = "Olive";
    private int _searchVersion;
    private int _loadRequestId;
    private int _nextPage = 1;
    private int _loadedItemCount;
    private bool _hasMoreItems;
    private bool _isLoading;
    private bool _isLoadingMore;
    private ICommandItem _emptyContent;

    public GifPickerPage(SettingsManager settingsManager)
    {
        _settingsManager = settingsManager;
        _emptyContent = InitialContent();
    }

    public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;

    public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;

    public IIconInfo Icon => _icon;

    public string Id => string.Empty;

    public string Name => "Olive GIF Picker";

    public string Title => _title;

    public OptionalColor AccentColor => default;

    public bool IsLoading
    {
        get => _isLoading;
        private set
        {
            if (_isLoading == value)
            {
                return;
            }

            _isLoading = value;
            RaisePropChanged(nameof(IsLoading));
        }
    }

    public string PlaceholderText => "Search for GIFs...";

    public bool ShowDetails => false;

    public bool HasMoreItems
    {
        get => _hasMoreItems;
        private set
        {
            if (_hasMoreItems == value)
            {
                return;
            }

            _hasMoreItems = value;
            RaisePropChanged(nameof(HasMoreItems));
        }
    }

    public IFilters? Filters => null;

    public IGridProperties GridProperties => _gridProperties;

    public ICommandItem EmptyContent
    {
        get => _emptyContent;
        private set
        {
            _emptyContent = value;
            RaisePropChanged(nameof(EmptyContent));
        }
    }

    public string? SearchText
    {
        get => _searchText;
        set
        {
            value ??= string.Empty;
            if (StringComparer.Ordinal.Equals(_searchText, value))
            {
                return;
            }

            _searchText = value;
            UpdateSearchText(value);
        }
    }

    public IListItem[] GetItems()
    {
        lock (_itemsLock)
        {
            return BuildDisplayItemsLocked();
        }
    }

    public void LoadMore()
    {
        if (_isLoadingMore || !HasMoreItems || string.IsNullOrWhiteSpace(_activeSearch))
        {
            return;
        }

        var apiKey = _settingsManager.KlipyApiKey;
        if (string.IsNullOrWhiteSpace(apiKey))
        {
            HasMoreItems = false;
            EmptyContent = MissingApiKeyContent();
            RaiseItemsChanged(DisplayItemCount());
            return;
        }

        var version = _searchVersion;
        _ = LoadPageBatchAsync(apiKey, _activeSearch, version, LoadMoreBatchPages, CancellationToken.None);
    }

    private void UpdateSearchText(string newSearch)
    {
        CancelSearch();

        var trimmedSearch = newSearch.Trim();
        if (string.IsNullOrWhiteSpace(trimmedSearch))
        {
            Interlocked.Increment(ref _searchVersion);
            Interlocked.Increment(ref _loadRequestId);
            IsLoading = false;
            _isLoadingMore = false;
            HasMoreItems = false;
            _activeSearch = string.Empty;
            _nextPage = 1;
            ReplaceItems([]);
            UpdateTitle();
            EmptyContent = _settingsManager.HasKlipyApiKey ? InitialContent() : MissingApiKeyContent();
            return;
        }

        var version = Interlocked.Increment(ref _searchVersion);
        Interlocked.Increment(ref _loadRequestId);
        var cancellation = new CancellationTokenSource();
        _searchCancellation = cancellation;

        _ = SearchAfterDebounceAsync(trimmedSearch, version, cancellation.Token);
    }

    private void CancelSearch()
    {
        var cancellation = _searchCancellation;
        _searchCancellation = null;
        if (cancellation is null)
        {
            return;
        }

        try
        {
            cancellation.Cancel();
        }
        catch (ObjectDisposedException)
        {
        }

        cancellation.Dispose();
    }

    private async Task SearchAfterDebounceAsync(string search, int version, CancellationToken cancellationToken)
    {
        try
        {
            await Task.Delay(SearchDebounceDelay, cancellationToken).ConfigureAwait(false);

            var apiKey = _settingsManager.KlipyApiKey;
            if (string.IsNullOrWhiteSpace(apiKey))
            {
                if (version != _searchVersion || cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                IsLoading = false;
                HasMoreItems = false;
                EmptyContent = MissingApiKeyContent();
                ReplaceItems([]);
                return;
            }

            if (version != _searchVersion || cancellationToken.IsCancellationRequested)
            {
                return;
            }

            _activeSearch = search;
            _nextPage = 1;
            _isLoadingMore = false;
            HasMoreItems = false;
            IsLoading = true;
            EmptyContent = LoadingContent(search);
            UpdateTitle();

            await LoadPageBatchAsync(apiKey, search, version, InitialBatchPages, cancellationToken).ConfigureAwait(false);
        }
        catch (OperationCanceledException)
        {
        }
        catch (Exception ex)
        {
            if (version == _searchVersion)
            {
                ShowSearchError(ex, resetItems: true);
            }
        }
    }

    private async Task LoadPageBatchAsync(string apiKey, string search, int version, int pageCount, CancellationToken cancellationToken)
    {
        if (_isLoadingMore)
        {
            return;
        }
        
        var loadRequestId = Interlocked.Increment(ref _loadRequestId);
        var startPage = _nextPage;
        var resetItems = startPage == 1;

        try
        {
            _isLoadingMore = true;
            IsLoading = true;

            var gifs = new List<KlipyGif>(PageSize * pageCount);
            var nextPage = startPage;
            var hasMore = false;

            for (var i = 0; i < pageCount && version == _searchVersion && !cancellationToken.IsCancellationRequested; i++)
            {
                var page = startPage + i;
                var result = await KlipyClient.SearchAsync(apiKey, search, page, PageSize, cancellationToken).ConfigureAwait(false);
                if (version != _searchVersion || cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                gifs.AddRange(result.Gifs);
                nextPage = page + 1;
                hasMore = result.HasMore;
                if (!hasMore)
                {
                    break;
                }
            }

            _nextPage = nextPage;
            HasMoreItems = hasMore;

            if (resetItems && gifs.Count == 0)
            {
                EmptyContent = new CommandItem(new NoOpCommand())
                {
                    Title = "No GIF found",
                    Subtitle = $"Try another search for \"{search}\".",
                    Icon = Icon,
                };
                ReplaceItems([]);
                return;
            }

            var newItems = gifs.Select(CreateItem).ToArray();
            if (resetItems)
            {
                ReplaceItems(newItems);
            }
            else
            {
                AppendItems(newItems);
            }
        }
        catch (Exception ex) when (ex is HttpRequestException or InvalidOperationException or TaskCanceledException)
        {
            if (version == _searchVersion)
            {
                ShowSearchError(ex, resetItems);
            }
        }
        catch (Exception ex)
        {
            if (version == _searchVersion)
            {
                ShowSearchError(ex, resetItems);
            }
        }
        finally
        {
            if (loadRequestId == _loadRequestId)
            {
                _isLoadingMore = false;
            }

            if (version == _searchVersion && loadRequestId == _loadRequestId)
            {
                IsLoading = false;
            }
        }
    }

    private void ShowSearchError(Exception ex, bool resetItems)
    {
        IsLoading = false;
        HasMoreItems = false;
        EmptyContent = new CommandItem(new NoOpCommand())
        {
            Title = "Search failed",
            Subtitle = ex.Message,
            Icon = Icon,
        };

        if (resetItems)
        {
            ReplaceItems([]);
        }
        else
        {
            RaiseItemsChanged(DisplayItemCount());
        }
    }

    private ListItem CreateItem(KlipyGif gif)
    {
        return new ListItem(new CopyGifCommand(gif, _cache))
        {
            Title = string.Empty,
            Subtitle = string.Empty,
            Icon = new IconInfo(gif.ThumbnailGifUrl.ToString()),
            MoreCommands = CreateLoadMoreContextItems(),
        };
    }

    private IContextItem[] CreateLoadMoreContextItems()
    {
        return [
            CreateLoadMoreContextItem("Load 30 more GIFs", VirtualKey.L),
        ];
    }

    private CommandContextItem CreateLoadMoreContextItem(string title, VirtualKey key)
    {
        return new CommandContextItem(new LoadMoreGifsCommand(LoadMore))
        {
            Title = title,
            Subtitle = "Append the next 30 results without moving the current selection",
            Icon = Icon,
            RequestedShortcut = KeyChordHelpers.FromModifiers(true, false, false, false, key, 0),
        };
    }

    private CommandItem InitialContent()
    {
        return new CommandItem(new NoOpCommand())
        {
            Title = _settingsManager.HasKlipyApiKey ? "Search GIFs" : "Klipy API key missing",
            Subtitle = _settingsManager.HasKlipyApiKey
                ? "Type something like thanks, excited, cat, or confused."
                : "Open Olive settings and set the Klipy API key.",
            Icon = Icon,
        };
    }

    private CommandItem LoadingContent(string search)
    {
        return new CommandItem(new NoOpCommand())
        {
            Title = "Loading GIFs...",
            Subtitle = $"Searching Klipy for \"{search}\"",
            Icon = Icon,
        };
    }

    private CommandItem MissingApiKeyContent()
    {
        return new CommandItem(new NoOpCommand())
        {
            Title = "Klipy API key missing",
            Subtitle = "Open Olive settings and paste your Klipy API key.",
            Icon = Icon,
        };
    }

    private void ReplaceItems(IListItem[] items)
    {
        int count;
        lock (_itemsLock)
        {
            _items.Clear();
            _items.AddRange(items);
            _loadedItemCount = items.Length;
            AddPlaceholderItemsLocked();
            count = DisplayItemCountLocked();
        }

        RaiseItemsChanged(count);
        UpdateTitle();
    }

    private void AppendItems(IListItem[] items)
    {
        int count;
        lock (_itemsLock)
        {
            RemovePlaceholderItemsLocked();
            _items.AddRange(items);
            _loadedItemCount += items.Length;
            AddPlaceholderItemsLocked();
            count = DisplayItemCountLocked();
        }

        RaiseItemsChanged(count);
        UpdateTitle();
    }

    private IListItem[] BuildDisplayItemsLocked()
    {
        return [.. _items];
    }

    private void AddPlaceholderItemsLocked()
    {
        if (!HasMoreItems)
        {
            return;
        }

        for (var i = 0; i < PageSize * LoadMoreBatchPages; i++)
        {
            _items.Add(CreateVirtualLoadingItem());
        }
    }

    private void RemovePlaceholderItemsLocked()
    {
        if (_items.Count > _loadedItemCount)
        {
            _items.RemoveRange(_loadedItemCount, _items.Count - _loadedItemCount);
        }
    }

    private ListItem CreateVirtualLoadingItem()
    {
        return new ListItem(new LoadMoreGifsCommand(LoadMore))
        {
            Title = "Loading more GIFs...",
            Subtitle = "Scroll here or press Ctrl+L to load the next 30",
            Icon = Icon,
            MoreCommands = CreateLoadMoreContextItems(),
        };
    }

    private int DisplayItemCount()
    {
        lock (_itemsLock)
        {
            return DisplayItemCountLocked();
        }
    }

    private int DisplayItemCountLocked()
    {
        return _items.Count;
    }

    private void RaiseItemsChanged(int count)
    {
        ItemsChanged?.Invoke(this, new ItemsChangedEventArgs(count));
    }

    private void RaisePropChanged(string propertyName)
    {
        PropChanged?.Invoke(this, new PropChangedEventArgs(propertyName));
    }

    private void UpdateTitle()
    {
        var loadedCount = LoadedItemCount();
        var title = string.IsNullOrWhiteSpace(_activeSearch) || loadedCount == 0
            ? "Olive"
            : HasMoreItems
                ? $"Olive - {loadedCount} GIFs loaded"
                : $"Olive - {loadedCount} GIFs loaded (all)";

        if (StringComparer.Ordinal.Equals(_title, title))
        {
            return;
        }

        _title = title;
        RaisePropChanged(nameof(Title));
    }

    private int LoadedItemCount()
    {
        lock (_itemsLock)
        {
            return _loadedItemCount;
        }
    }

    private sealed partial class LoadMoreGifsCommand : InvokableCommand
    {
        private readonly Action _loadMore;

        public LoadMoreGifsCommand(Action loadMore)
        {
            _loadMore = loadMore;
            Name = "Load 30 more GIFs";
            Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.png");
        }

        public override ICommandResult Invoke()
        {
            _loadMore();
            return CommandResult.KeepOpen();
        }
    }

}
