using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms.Integration;
using System.Windows.Threading;
using MarcusLaw.OutlookAddin.Core.Services;
using MarcusLaw.OutlookAddin.UI.ViewModels;
using MarcusLaw.OutlookAddin.UI.Views;
using Microsoft.Office.Tools;
using Serilog;
using Office = Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace OutlookAddin.Services
{
    /// <summary>
    /// Owns the per-Explorer "Case Info" task pane. For each active Explorer
    /// window we host a single <see cref="ReadingPaneView"/> wrapped in
    /// <see cref="ElementHost"/> and a <see cref="ExplorerSelectionHandler"/>
    /// that drives lookups.
    /// </summary>
    public sealed class SidebarController : IDisposable
    {
        private const string PaneTitle = "Klear · פרטי תיק";
        private const int DefaultWidth = 320;

        private readonly CustomTaskPaneCollection _taskPanes;
        private readonly Outlook.Application _app;
        private readonly IMatchingService _matchingService;
        private readonly AddInHost _host;
        private readonly Dispatcher _dispatcher;
        private readonly ILogger _logger;

        private readonly Dictionary<int, ExplorerBinding> _bindings = new Dictionary<int, ExplorerBinding>();
        private Outlook.Explorers? _explorers;

        public SidebarController(
            CustomTaskPaneCollection taskPanes,
            Outlook.Application app,
            IMatchingService matchingService,
            AddInHost host,
            Dispatcher dispatcher,
            ILogger logger)
        {
            _taskPanes = taskPanes ?? throw new ArgumentNullException(nameof(taskPanes));
            _app = app ?? throw new ArgumentNullException(nameof(app));
            _matchingService = matchingService ?? throw new ArgumentNullException(nameof(matchingService));
            _host = host ?? throw new ArgumentNullException(nameof(host));
            _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public void Initialize()
        {
            try
            {
                _explorers = _app.Explorers;
                _explorers.NewExplorer += OnNewExplorer;

                // Attach to whatever Explorer is open right now.
                var active = _app.ActiveExplorer();
                if (active != null)
                {
                    AttachToExplorer(active);
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "SidebarController.Initialize failed");
            }
        }

        private void OnNewExplorer(Outlook.Explorer explorer)
        {
            try { AttachToExplorer(explorer); }
            catch (Exception ex) { _logger.Warning(ex, "SidebarController.OnNewExplorer failed"); }
        }

        private void AttachToExplorer(Outlook.Explorer explorer)
        {
            var key = ExplorerKey(explorer);
            if (_bindings.ContainsKey(key)) return;

            var view = new ReadingPaneView();
            var vm = new ReadingPaneViewModel(_matchingService, _logger)
            {
                EspoCrmBaseUrl = _host.Settings.EspoCrmUrl
            };
            view.DataContext = vm;

            // CustomTaskPanes.Add requires a System.Windows.Forms.UserControl,
            // so we host the WPF UserControl inside ElementHost which itself
            // lives inside a thin WinForms UserControl wrapper.
            var element = new ElementHost
            {
                Child = view,
                Dock = System.Windows.Forms.DockStyle.Fill
            };
            var hostControl = new System.Windows.Forms.UserControl();
            hostControl.Controls.Add(element);

            var pane = _taskPanes.Add(hostControl, PaneTitle, explorer);
            pane.DockPosition = Office.MsoCTPDockPosition.msoCTPDockPositionRight;
            pane.Width = DefaultWidth;
            pane.Visible = true;

            var handler = new ExplorerSelectionHandler(explorer, vm, _dispatcher, _logger);

            vm.FileRequested += async (s, e) =>
            {
                try { await _host.LaunchFileToCaseAsync(e.PreselectedMatch).ConfigureAwait(true); }
                catch (Exception ex) { _logger.Warning(ex, "Sidebar file-request handler failed"); }
            };
            vm.OpenInBrowserRequested += (s, url) => TryOpenInBrowser(url);

            // Outlook.Explorer exposes BOTH a Close() method and a Close event
            // on the same name; cast through the events interface to bind the
            // event handler.
            ((Outlook.ExplorerEvents_10_Event)explorer).Close += () => DetachExplorer(key);

            _bindings[key] = new ExplorerBinding(pane, hostControl, element, vm, handler, explorer);

            // Kick a first lookup based on whatever is selected when the pane opens.
            handler.TriggerNow();
        }

        private void DetachExplorer(int key)
        {
            if (!_bindings.TryGetValue(key, out var binding)) return;
            _bindings.Remove(key);
            binding.Dispose();
        }

        private static int ExplorerKey(Outlook.Explorer explorer)
        {
            // RuntimeCallableWrapper identity is unstable across RCW reads,
            // but the underlying COM pointer is stable; use it as the dictionary key.
            var ptr = Marshal.GetIUnknownForObject(explorer);
            try { return ptr.GetHashCode(); }
            finally { Marshal.Release(ptr); }
        }

        /// <summary>
        /// Brings the sidebar back for the current Explorer — used by the
        /// ribbon "פרטי תיק" button so the user can re-open the pane after
        /// closing its X.
        /// </summary>
        public void ShowForActiveExplorer()
        {
            try
            {
                var explorer = _app.ActiveExplorer();
                if (explorer == null) return;

                var key = ExplorerKey(explorer);
                if (_bindings.TryGetValue(key, out var binding))
                {
                    binding.Pane.Visible = true;
                    return;
                }
                AttachToExplorer(explorer);
            }
            catch (Exception ex)
            {
                _logger.Warning(ex, "SidebarController.ShowForActiveExplorer failed");
            }
        }

        private void TryOpenInBrowser(string url)
        {
            try
            {
                Process.Start(new ProcessStartInfo
                {
                    FileName = url,
                    UseShellExecute = true
                });
            }
            catch (Exception ex)
            {
                _logger.Warning(ex, "TryOpenInBrowser failed for {Url}", url);
            }
        }

        public void Dispose()
        {
            try
            {
                if (_explorers != null)
                {
                    _explorers.NewExplorer -= OnNewExplorer;
                }
            }
            catch { /* ignore */ }

            foreach (var binding in _bindings.Values)
            {
                binding.Dispose();
            }
            _bindings.Clear();
        }

        private sealed class ExplorerBinding : IDisposable
        {
            public CustomTaskPane Pane { get; }
            private readonly System.Windows.Forms.UserControl _hostControl;
            private readonly ElementHost _element;
            private readonly ExplorerSelectionHandler _handler;
            private readonly Outlook.Explorer _explorer;

            public ReadingPaneViewModel ViewModel { get; }

            public ExplorerBinding(
                CustomTaskPane pane,
                System.Windows.Forms.UserControl hostControl,
                ElementHost element,
                ReadingPaneViewModel viewModel,
                ExplorerSelectionHandler handler,
                Outlook.Explorer explorer)
            {
                Pane = pane;
                _hostControl = hostControl;
                _element = element;
                ViewModel = viewModel;
                _handler = handler;
                _explorer = explorer;
            }

            public void Dispose()
            {
                try { _handler.Dispose(); } catch { /* ignore */ }
                try { Pane.Visible = false; } catch { /* ignore */ }
                try { _element.Dispose(); } catch { /* ignore */ }
                try { _hostControl.Dispose(); } catch { /* ignore */ }
            }
        }
    }
}
