using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.WinUI;
using EmailValidation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Interfaces;
using Wino.Mail.WinUI;

namespace Wino.Controls;

/// <summary>
/// Contact avatar control built on top of PersonPicture.
/// Priority:
/// 1) AccountContact file-based picture
/// 2) Gravatar thumbnail (if enabled)
/// 3) Initials from display name fallback
/// </summary>
public sealed partial class ImagePreviewControl : PersonPicture
{
    private sealed record RefreshSnapshot(string DisplayName, string Address, Guid? ContactPictureFileId);

    private static readonly TimeSpan RefreshDebounceDuration = TimeSpan.FromMilliseconds(40);

    [GeneratedDependencyProperty]
    public partial IMailItemDisplayInformation? MailItemInformation { get; set; }

    [GeneratedDependencyProperty]
    public partial AccountContact? PreviewContact { get; set; }

    [GeneratedDependencyProperty]
    public partial string? Address { get; set; }

    [GeneratedDependencyProperty]
    public partial string? DisplayNameOverride { get; set; }

    private readonly IThumbnailService? _thumbnailService;
    private readonly IPreferencesService? _preferencesService;
    private readonly IContactPictureFileService? _contactPictureFileService;
    private INotifyPropertyChanged? _mailItemInformationPropertySource;
    private CancellationTokenSource? _refreshCancellationTokenSource;
    private CancellationTokenSource? _scheduledRefreshCancellationTokenSource;
    private long _refreshVersion;

    public ImagePreviewControl()
    {
        DefaultStyleKey = typeof(PersonPicture);

        try
        {
            _thumbnailService = App.Current.Services.GetService<IThumbnailService>();
            _preferencesService = App.Current.Services.GetService<IPreferencesService>();
            _contactPictureFileService = App.Current.Services.GetService<IContactPictureFileService>();
        }
        catch
        {
            // Keep control functional in design-time/test contexts without service provider.
        }

        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    partial void OnMailItemInformationPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        if (_mailItemInformationPropertySource != null)
        {
            _mailItemInformationPropertySource.PropertyChanged -= MailItemInformationPropertyChanged;
            _mailItemInformationPropertySource = null;
        }

        if (e.NewValue is INotifyPropertyChanged observableMailItemInformation)
        {
            _mailItemInformationPropertySource = observableMailItemInformation;
            _mailItemInformationPropertySource.PropertyChanged += MailItemInformationPropertyChanged;
        }

        RequestRefresh();
    }

    partial void OnPreviewContactPropertyChanged(DependencyPropertyChangedEventArgs e) => RequestRefresh();
    partial void OnAddressPropertyChanged(DependencyPropertyChangedEventArgs e) => RequestRefresh();
    partial void OnDisplayNameOverridePropertyChanged(DependencyPropertyChangedEventArgs e) => RequestRefresh();

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        RequestRefresh();
    }

    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        if (_mailItemInformationPropertySource != null)
        {
            _mailItemInformationPropertySource.PropertyChanged -= MailItemInformationPropertyChanged;
            _mailItemInformationPropertySource = null;
        }

        CancelScheduledRefresh();
        CancelActiveRefresh();
    }

    private void MailItemInformationPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        // Refresh only for fields that affect avatar image or initials.
        if (string.IsNullOrEmpty(e.PropertyName)
            || e.PropertyName == nameof(IMailItemDisplayInformation.ContactPictureFileId)
            || e.PropertyName == nameof(IMailItemDisplayInformation.SenderContact)
            || e.PropertyName == nameof(IMailItemDisplayInformation.FromName)
            || e.PropertyName == nameof(IMailItemDisplayInformation.FromAddress)
            || e.PropertyName == nameof(IMailItemDisplayInformation.ThumbnailUpdatedEvent))
        {
            RequestRefresh();
        }
    }

    private void RequestRefresh()
    {
        if (DispatcherQueue == null || DispatcherQueue.HasThreadAccess)
        {
            QueueRefresh();
            return;
        }

        DispatcherQueue.TryEnqueue(QueueRefresh);
    }

    private void QueueRefresh()
    {
        if (!IsLoaded)
            return;

        CancelScheduledRefresh();

        var cts = new CancellationTokenSource();
        _scheduledRefreshCancellationTokenSource = cts;

        _ = DebounceAndRefreshAsync(cts.Token);
    }

    private async Task DebounceAndRefreshAsync(CancellationToken cancellationToken)
    {
        try
        {
            await Task.Delay(RefreshDebounceDuration, cancellationToken).ConfigureAwait(false);
        }
        catch (OperationCanceledException)
        {
            return;
        }

        StartRefresh();
    }

    private void StartRefresh()
    {
        CancelActiveRefresh();

        var cts = new CancellationTokenSource();
        _refreshCancellationTokenSource = cts;
        var refreshVersion = Interlocked.Increment(ref _refreshVersion);
        _ = RefreshAsync(refreshVersion, cts.Token);
    }

    private void CancelScheduledRefresh()
    {
        var cts = _scheduledRefreshCancellationTokenSource;
        _scheduledRefreshCancellationTokenSource = null;

        if (cts != null && !cts.IsCancellationRequested)
        {
            cts.Cancel();
        }

        cts?.Dispose();
    }

    private void CancelActiveRefresh()
    {
        var cts = _refreshCancellationTokenSource;
        _refreshCancellationTokenSource = null;

        if (cts != null && !cts.IsCancellationRequested)
        {
            cts.Cancel();
        }

        cts?.Dispose();
    }

    private async Task RefreshAsync(long refreshVersion, CancellationToken cancellationToken)
    {
        try
        {
            var snapshot = await CaptureSnapshotAsync(refreshVersion, cancellationToken).ConfigureAwait(false);
            if (snapshot == null)
                return;

            await ApplyInitialVisualStateAsync(snapshot.DisplayName, refreshVersion, cancellationToken).ConfigureAwait(false);

            // Skip all picture loading if the user has disabled sender pictures.
            if (_preferencesService?.IsShowSenderPicturesEnabled == false)
                return;

            // 1) File-based contact picture (preferred — native WIC decode, no base64 overhead).
            if (snapshot.ContactPictureFileId.HasValue && _contactPictureFileService != null)
            {
                var filePath = _contactPictureFileService.GetContactPicturePath(snapshot.ContactPictureFileId.Value);
                if (!string.IsNullOrEmpty(filePath))
                {
                    var fileBitmap = await CreateBitmapFromFileAsync(filePath, cancellationToken).ConfigureAwait(false);
                    if (fileBitmap != null)
                    {
                        await ApplyProfilePictureAsync(fileBitmap, refreshVersion, cancellationToken).ConfigureAwait(false);
                        return;
                    }
                }
            }

            // 2) Gravatar lookup through thumbnail service (if enabled).
            if (_preferencesService?.IsGravatarEnabled == true &&
                _thumbnailService != null &&
                !string.IsNullOrWhiteSpace(snapshot.Address) &&
                EmailValidator.Validate(snapshot.Address))
            {
                var thumbnailBase64 = await _thumbnailService
                    .GetThumbnailAsync(snapshot.Address.Trim().ToLowerInvariant(), awaitLoad: true)
                    .ConfigureAwait(false);

                if (!string.IsNullOrWhiteSpace(thumbnailBase64))
                {
                    var thumbnailBitmap = await CreateBitmapFromBase64Async(thumbnailBase64, cancellationToken).ConfigureAwait(false);
                    if (thumbnailBitmap != null)
                    {
                        await ApplyProfilePictureAsync(thumbnailBitmap, refreshVersion, cancellationToken).ConfigureAwait(false);
                        return;
                    }
                }
            }

            // 3) Initials fallback is already in place via DisplayName + ProfilePicture = null.
        }
        catch (OperationCanceledException)
        {
            // Expected during virtualization/recycling.
        }
        catch
        {
            // Keep fallback initials if decoding/network fails.
        }
    }

    // DependencyProperty-backed values must be read on UI thread once, then used off-thread.
    private async Task<RefreshSnapshot?> CaptureSnapshotAsync(long refreshVersion, CancellationToken cancellationToken)
    {
        return await ExecuteOnUiThreadAsync(() =>
        {
            if (!IsActiveRefresh(refreshVersion, cancellationToken))
                return null;

            var address = ResolveAddress();
            var displayName = ResolveDisplayName(address);
            var contactPictureFileId = PreviewContact?.ContactPictureFileId
                                       ?? MailItemInformation?.SenderContact?.ContactPictureFileId
                                       ?? MailItemInformation?.ContactPictureFileId;

            return new RefreshSnapshot(displayName, address, contactPictureFileId);
        }).ConfigureAwait(false);
    }

    private string ResolveAddress()
    {
        if (!string.IsNullOrWhiteSpace(PreviewContact?.Address))
            return PreviewContact.Address.Trim();

        if (!string.IsNullOrWhiteSpace(Address))
            return Address.Trim();

        if (MailItemInformation == null)
            return string.Empty;

        var contactAddress = MailItemInformation?.SenderContact?.Address;
        if (!string.IsNullOrWhiteSpace(contactAddress))
            return contactAddress.Trim();

        if (!string.IsNullOrWhiteSpace(MailItemInformation?.FromAddress))
            return MailItemInformation.FromAddress.Trim();

        return string.Empty;
    }

    private string ResolveDisplayName(string resolvedAddress)
    {
        if (!string.IsNullOrWhiteSpace(PreviewContact?.Name))
            return PreviewContact.Name.Trim();

        if (!string.IsNullOrWhiteSpace(DisplayNameOverride))
            return DisplayNameOverride.Trim();

        var contactName = MailItemInformation?.SenderContact?.Name;
        if (!string.IsNullOrWhiteSpace(contactName))
            return contactName.Trim();

        if (!string.IsNullOrWhiteSpace(MailItemInformation?.FromName))
            return MailItemInformation.FromName.Trim();

        return resolvedAddress.Trim();
    }
    private async Task ApplyInitialVisualStateAsync(string displayName, long refreshVersion, CancellationToken cancellationToken)
    {
        await ExecuteOnUiThreadAsync(() =>
        {
            if (!IsActiveRefresh(refreshVersion, cancellationToken))
                return;

            DisplayName = displayName;
            Initials = null;
            ProfilePicture = null;
        }).ConfigureAwait(false);
    }

    private async Task ApplyProfilePictureAsync(BitmapImage bitmapImage, long refreshVersion, CancellationToken cancellationToken)
    {
        await ExecuteOnUiThreadAsync(() =>
        {
            if (!IsActiveRefresh(refreshVersion, cancellationToken))
                return;

            DisplayName = string.Empty;
            Initials = string.Empty;
            ProfilePicture = bitmapImage;
        }).ConfigureAwait(false);
    }

    private bool IsActiveRefresh(long refreshVersion, CancellationToken cancellationToken)
        => !cancellationToken.IsCancellationRequested && refreshVersion == _refreshVersion;

    private async Task ExecuteOnUiThreadAsync(Action action)
    {
        if (DispatcherQueue == null || DispatcherQueue.HasThreadAccess)
        {
            action();
            return;
        }

        var completion = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);

        var enqueued = DispatcherQueue.TryEnqueue(() =>
        {
            try
            {
                action();
                completion.TrySetResult(null);
            }
            catch (Exception ex)
            {
                completion.TrySetException(ex);
            }
        });

        if (!enqueued)
        {
            completion.TrySetException(new InvalidOperationException("Failed to dispatch UI update."));
        }

        await completion.Task.ConfigureAwait(false);
    }

    private async Task<T> ExecuteOnUiThreadAsync<T>(Func<T> func)
    {
        if (DispatcherQueue == null || DispatcherQueue.HasThreadAccess)
        {
            return func();
        }

        var completion = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);

        var enqueued = DispatcherQueue.TryEnqueue(() =>
        {
            try
            {
                completion.TrySetResult(func());
            }
            catch (Exception ex)
            {
                completion.TrySetException(ex);
            }
        });

        if (!enqueued)
        {
            completion.TrySetException(new InvalidOperationException("Failed to dispatch UI update."));
        }

        return await completion.Task.ConfigureAwait(false);
    }

    private async Task<BitmapImage?> CreateBitmapFromFileAsync(string filePath, CancellationToken cancellationToken)
    {
        byte[] bytes;
        try
        {
            bytes = await File.ReadAllBytesAsync(filePath, cancellationToken).ConfigureAwait(false);
        }
        catch
        {
            return null;
        }

        cancellationToken.ThrowIfCancellationRequested();

        return await ExecuteOnUiThreadAsync(() =>
        {
            cancellationToken.ThrowIfCancellationRequested();

            using var memoryStream = new MemoryStream(bytes);
            var bitmapImage = new BitmapImage();
            bitmapImage.SetSource(memoryStream.AsRandomAccessStream());
            return bitmapImage;
        }).ConfigureAwait(false);
    }

    private async Task<BitmapImage?> CreateBitmapFromBase64Async(string base64, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(base64))
            return null;

        byte[] bytes;

        try
        {
            bytes = await Task.Run(() => Convert.FromBase64String(base64), cancellationToken).ConfigureAwait(false);
        }
        catch
        {
            return null;
        }

        cancellationToken.ThrowIfCancellationRequested();

        return await ExecuteOnUiThreadAsync(() =>
        {
            cancellationToken.ThrowIfCancellationRequested();

            using var memoryStream = new MemoryStream(bytes);
            var bitmapImage = new BitmapImage();
            bitmapImage.SetSource(memoryStream.AsRandomAccessStream());
            return bitmapImage;
        }).ConfigureAwait(false);
    }

}
