using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WinUIEx;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;

namespace Wino.Mail.WinUI.Services;

public class WinoWindowManager : IWinoWindowManager
{
    public event EventHandler<WindowEx?>? ActiveWindowChanged;
    public event EventHandler<WindowEx>? WindowRemoved;

    private readonly object _syncLock = new();
    private readonly Dictionary<(WinoWindowKind Kind, string Name), WindowEx> _windows = [];
    private readonly Dictionary<WindowEx, (WinoWindowKind Kind, string Name)> _windowKeys = [];
    private readonly Dictionary<(WinoWindowKind Kind, string Name), Frame> _primaryNavigationFrames = [];

    public WindowEx? ActiveWindow { get; private set; }

    public WindowEx CreateWindow(WinoWindowKind kind, Func<WindowEx> factory, string? name = null)
    {
        var key = CreateKey(kind, name);

        lock (_syncLock)
        {
            if (_windows.TryGetValue(key, out var existingWindow))
            {
                ActiveWindow = existingWindow;
                ActiveWindowChanged?.Invoke(this, existingWindow);
                return existingWindow;
            }
        }

        var newWindow = factory();

        lock (_syncLock)
        {
            if (_windows.TryGetValue(key, out var existingWindow))
            {
                ActiveWindow = existingWindow;
                ActiveWindowChanged?.Invoke(this, existingWindow);
                return existingWindow;
            }

            TrackWindow(key, newWindow);
            ActiveWindow = newWindow;
            ActiveWindowChanged?.Invoke(this, newWindow);
            return newWindow;
        }
    }

    public WindowEx? GetWindow(WinoWindowKind kind, string? name = null)
    {
        lock (_syncLock)
        {
            _windows.TryGetValue(CreateKey(kind, name), out var window);
            return window;
        }
    }

    public WindowEx? GetWindow(string name)
    {
        var normalizedName = NormalizeName(name);

        lock (_syncLock)
        {
            return _windows
                .Where(x => x.Key.Name.Equals(normalizedName, StringComparison.OrdinalIgnoreCase))
                .Select(x => x.Value)
                .FirstOrDefault();
        }
    }

    public void ActivateWindow(WindowEx window)
    {
        window.Show();
        window.BringToFront();
        window.Activate();

        lock (_syncLock)
        {
            ActiveWindow = window;
        }

        ActiveWindowChanged?.Invoke(this, window);
    }

    public bool ActivateWindow(WinoWindowKind kind, string? name = null)
    {
        var window = GetWindow(kind, name);
        if (window == null)
            return false;

        ActivateWindow(window);
        return true;
    }

    public void HideWindow(WindowEx window)
    {
        window.Hide();

        lock (_syncLock)
        {
            if (ReferenceEquals(ActiveWindow, window))
            {
                ActiveWindow = null;
                ActiveWindowChanged?.Invoke(this, null);
            }
        }
    }

    public bool HideWindow(WinoWindowKind kind, string? name = null)
    {
        var window = GetWindow(kind, name);
        if (window == null)
            return false;

        HideWindow(window);
        return true;
    }

    public void SetPrimaryNavigationFrame(WinoWindowKind kind, Frame frame, string? name = null)
    {
        lock (_syncLock)
        {
            _primaryNavigationFrames[CreateKey(kind, name)] = frame;
        }
    }

    public Frame? GetPrimaryNavigationFrame(WinoWindowKind kind, string? name = null)
    {
        lock (_syncLock)
        {
            _primaryNavigationFrames.TryGetValue(CreateKey(kind, name), out var frame);
            return frame;
        }
    }

    private void TrackWindow((WinoWindowKind Kind, string Name) key, WindowEx window)
    {
        _windows[key] = window;
        _windowKeys[window] = key;
        window.Activated += WindowActivated;
        window.Closed += WindowClosed;
    }

    private void WindowActivated(object sender, WindowActivatedEventArgs args)
    {
        if (sender is not WindowEx window)
            return;

        if (args.WindowActivationState == WindowActivationState.Deactivated)
            return;

        lock (_syncLock)
        {
            if (_windowKeys.ContainsKey(window))
            {
                ActiveWindow = window;
                ActiveWindowChanged?.Invoke(this, window);
            }
        }
    }

    private void WindowClosed(object sender, WindowEventArgs args)
    {
        if (sender is not WindowEx window)
            return;

        lock (_syncLock)
        {
            if (!_windowKeys.TryGetValue(window, out var key))
                return;

            window.Activated -= WindowActivated;
            window.Closed -= WindowClosed;

            _windowKeys.Remove(window);
            _windows.Remove(key);
            _primaryNavigationFrames.Remove(key);
            WindowRemoved?.Invoke(this, window);

            if (ReferenceEquals(ActiveWindow, window))
            {
                ActiveWindow = null;
                ActiveWindowChanged?.Invoke(this, null);
            }
        }
    }

    public void CloseAllWindows()
    {
        List<WindowEx> windows;
        lock (_syncLock)
        {
            windows = _windows.Values.Distinct().ToList();
        }

        foreach (var window in windows)
        {
            try
            {
                window.Activated -= WindowActivated;
                window.Closed -= WindowClosed;
                window.Close();
            }
            catch
            {
                // Best effort shutdown for all tracked windows.
            }
        }

        lock (_syncLock)
        {
            _windowKeys.Clear();
            _windows.Clear();
            _primaryNavigationFrames.Clear();
            ActiveWindow = null;
        }

        ActiveWindowChanged?.Invoke(this, null);
    }

    private static (WinoWindowKind Kind, string Name) CreateKey(WinoWindowKind kind, string? name)
    {
        var resolvedName = NormalizeName(name ?? kind.ToString());
        return (kind, string.IsNullOrWhiteSpace(resolvedName) ? kind.ToString() : resolvedName);
    }

    private static string NormalizeName(string name) => name.Trim();
}
