using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Serilog;
using ZB.MOM.WW.OtOpcUa.Client.Shared;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;
using ZB.MOM.WW.OtOpcUa.Client.UI.Services;

namespace ZB.MOM.WW.OtOpcUa.Client.UI.ViewModels;

/// <summary>
///     Main window ViewModel coordinating all panels.
/// </summary>
public partial class MainWindowViewModel : ObservableObject, IDisposable
{
    private static readonly ILogger Logger = Log.ForContext<MainWindowViewModel>();

    private readonly IUiDispatcher _dispatcher;
    private readonly IOpcUaClientServiceFactory _factory;
    private readonly ISettingsService _settingsService;
    private IOpcUaClientService? _service;
    private List<string> _savedSubscribedNodes = [];
    private string? _savedAlarmSourceNodeId;

    [ObservableProperty] private bool _autoAcceptCertificates = true;

    [ObservableProperty] private string _certificateStorePath = ClientStoragePaths.GetPkiPath();

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(ConnectCommand))]
    [NotifyCanExecuteChangedFor(nameof(DisconnectCommand))]
    private ConnectionState _connectionState = ConnectionState.Disconnected;

    [ObservableProperty] private string _endpointUrl = "opc.tcp://localhost:4840";

    [ObservableProperty] private string? _failoverUrls;

    [ObservableProperty] private bool _isHistoryEnabledForSelection;

    [ObservableProperty] private string? _password;

    [ObservableProperty] private RedundancyInfo? _redundancyInfo;

    [ObservableProperty] private SecurityMode _selectedSecurityMode = SecurityMode.None;

    [ObservableProperty] private int _selectedTabIndex;

    [ObservableProperty] private TreeNodeViewModel? _selectedTreeNode;

    [ObservableProperty] private string _sessionLabel = string.Empty;

    [ObservableProperty] private int _sessionTimeoutSeconds = 60;

    [ObservableProperty] private string _statusMessage = "Disconnected";

    [ObservableProperty] private int _subscriptionCount;

    [ObservableProperty] private int _activeAlarmCount;

    [ObservableProperty] private string? _username;

    /// <summary>
    /// Creates the main shell view model that coordinates connection state, browsing, subscriptions, alarms, history, and persisted settings.
    /// </summary>
    /// <param name="factory">Creates the shared OPC UA client service used by all panels.</param>
    /// <param name="dispatcher">Marshals service callbacks back onto the UI thread.</param>
    /// <param name="settingsService">Loads and saves persisted user connection settings.</param>
    public MainWindowViewModel(IOpcUaClientServiceFactory factory, IUiDispatcher dispatcher,
        ISettingsService? settingsService = null)
    {
        _factory = factory;
        _dispatcher = dispatcher;
        _settingsService = settingsService ?? new JsonSettingsService();

        LoadSettings();
    }

    /// <summary>All available security modes.</summary>
    public IReadOnlyList<SecurityMode> SecurityModes { get; } = Enum.GetValues<SecurityMode>();

    /// <summary>
    /// Gets a value indicating whether the shell is currently connected to an OPC UA endpoint.
    /// </summary>
    public bool IsConnected => ConnectionState == ConnectionState.Connected;

    /// <summary>The currently selected tree nodes (supports multi-select).</summary>
    public ObservableCollection<TreeNodeViewModel> SelectedTreeNodes { get; } = [];

    /// <summary>
    /// Gets the browse-tree panel view model for the address-space explorer.
    /// </summary>
    public BrowseTreeViewModel? BrowseTree { get; private set; }

    /// <summary>
    /// Gets the read/write panel view model for point operations against the selected node.
    /// </summary>
    public ReadWriteViewModel? ReadWrite { get; private set; }

    /// <summary>
    /// Gets the subscriptions panel view model for live data monitoring.
    /// </summary>
    public SubscriptionsViewModel? Subscriptions { get; private set; }

    /// <summary>
    /// Gets the alarms panel view model for active-condition monitoring and acknowledgment.
    /// </summary>
    public AlarmsViewModel? Alarms { get; private set; }

    /// <summary>
    /// Gets the history panel view model for raw and aggregate history queries.
    /// </summary>
    public HistoryViewModel? History { get; private set; }

    /// <summary>
    /// Gets the subscriptions tab header, including the current active subscription count when nonzero.
    /// </summary>
    public string SubscriptionsTabHeader => SubscriptionCount > 0
        ? $"Subscriptions ({SubscriptionCount})"
        : "Subscriptions";

    /// <summary>
    /// Gets the alarms tab header, including the current active alarm count when nonzero.
    /// </summary>
    public string AlarmsTabHeader => ActiveAlarmCount > 0
        ? $"Alarms ({ActiveAlarmCount})"
        : "Alarms";

    private void InitializeService()
    {
        if (_service != null) return;

        _service = _factory.Create();
        _service.ConnectionStateChanged += OnConnectionStateChanged;

        BrowseTree = new BrowseTreeViewModel(_service, _dispatcher);
        ReadWrite = new ReadWriteViewModel(_service, _dispatcher);
        Subscriptions = new SubscriptionsViewModel(_service, _dispatcher);
        Alarms = new AlarmsViewModel(_service, _dispatcher);
        Alarms.PropertyChanged += (_, args) =>
        {
            if (args.PropertyName == nameof(AlarmsViewModel.ActiveAlarmCount))
                _dispatcher.Post(() => ActiveAlarmCount = Alarms.ActiveAlarmCount);
            else if (args.PropertyName == nameof(AlarmsViewModel.StatusMessage)
                     && !string.IsNullOrEmpty(Alarms.StatusMessage))
                _dispatcher.Post(() => StatusMessage = Alarms.StatusMessage!);
        };
        Subscriptions.PropertyChanged += (_, args) =>
        {
            if (args.PropertyName == nameof(SubscriptionsViewModel.StatusMessage)
                && !string.IsNullOrEmpty(Subscriptions.StatusMessage))
                _dispatcher.Post(() => StatusMessage = Subscriptions.StatusMessage!);
        };
        History = new HistoryViewModel(_service, _dispatcher);

        OnPropertyChanged(nameof(BrowseTree));
        OnPropertyChanged(nameof(ReadWrite));
        OnPropertyChanged(nameof(Subscriptions));
        OnPropertyChanged(nameof(Alarms));
        OnPropertyChanged(nameof(History));
    }

    private void OnConnectionStateChanged(object? sender, ConnectionStateChangedEventArgs e)
    {
        _dispatcher.Post(() => { ConnectionState = e.NewState; });
    }

    partial void OnConnectionStateChanged(ConnectionState value)
    {
        OnPropertyChanged(nameof(IsConnected));

        var connected = value == ConnectionState.Connected;
        if (ReadWrite != null) ReadWrite.IsConnected = connected;
        if (Subscriptions != null) Subscriptions.IsConnected = connected;
        if (Alarms != null) Alarms.IsConnected = connected;
        if (History != null) History.IsConnected = connected;

        switch (value)
        {
            case ConnectionState.Connected:
                StatusMessage = $"Connected to {EndpointUrl}";
                Subscriptions?.Reattach();
                Alarms?.Reattach();
                break;
            case ConnectionState.Reconnecting:
                StatusMessage = "Reconnecting...";
                break;
            case ConnectionState.Connecting:
                StatusMessage = "Connecting...";
                break;
            case ConnectionState.Disconnected:
                StatusMessage = "Disconnected";
                SessionLabel = string.Empty;
                RedundancyInfo = null;
                Subscriptions?.Teardown();
                Alarms?.Teardown();
                BrowseTree?.Clear();
                ReadWrite?.Clear();
                Subscriptions?.Clear();
                Alarms?.Clear();
                History?.Clear();
                SubscriptionCount = 0;
                ActiveAlarmCount = 0;
                break;
        }
    }

    partial void OnSelectedTreeNodeChanged(TreeNodeViewModel? value)
    {
        if (ReadWrite != null) ReadWrite.SelectedNodeId = value?.NodeId;
        if (History != null) History.SelectedNodeId = value?.NodeId;
    }

    partial void OnSubscriptionCountChanged(int value)
    {
        OnPropertyChanged(nameof(SubscriptionsTabHeader));
    }

    partial void OnActiveAlarmCountChanged(int value)
    {
        OnPropertyChanged(nameof(AlarmsTabHeader));
    }

    private bool CanConnect()
    {
        return ConnectionState == ConnectionState.Disconnected;
    }

    [RelayCommand(CanExecute = nameof(CanConnect))]
    private async Task ConnectAsync()
    {
        try
        {
            ConnectionState = ConnectionState.Connecting;
            StatusMessage = "Connecting...";

            InitializeService();

            var settings = new ConnectionSettings
            {
                EndpointUrl = EndpointUrl,
                Username = Username,
                Password = Password,
                SecurityMode = SelectedSecurityMode,
                FailoverUrls = ParseFailoverUrls(FailoverUrls),
                SessionTimeoutSeconds = SessionTimeoutSeconds,
                AutoAcceptCertificates = AutoAcceptCertificates,
                CertificateStorePath = CertificateStorePath
            };
            settings.Validate();

            var info = await _service!.ConnectAsync(settings);

            _dispatcher.Post(() =>
            {
                ConnectionState = ConnectionState.Connected;
                SessionLabel = $"{info.ServerName} | Session: {info.SessionName} ({info.SessionId})";
            });

            // Load redundancy info — the server may not implement the redundancy facet, in which
            // case we leave RedundancyInfo null but log so a field diagnosis can tell the difference
            // between "facet not advertised" and "facet errored". The connection itself stays up.
            try
            {
                var redundancy = await _service!.GetRedundancyInfoAsync();
                _dispatcher.Post(() => RedundancyInfo = redundancy);
            }
            catch (Exception redundancyEx)
            {
                Logger.Information(redundancyEx, "GetRedundancyInfo unavailable on this server");
            }

            // Load root nodes
            if (BrowseTree != null) await BrowseTree.LoadRootsAsync();

            // Restore saved subscriptions
            if (_savedSubscribedNodes.Count > 0 && Subscriptions != null)
            {
                await Subscriptions.RestoreSubscriptionsAsync(_savedSubscribedNodes);
                SubscriptionCount = Subscriptions.SubscriptionCount;
            }

            // Restore saved alarm subscription
            if (!string.IsNullOrEmpty(_savedAlarmSourceNodeId) && Alarms != null)
                await Alarms.RestoreAlarmSubscriptionAsync(_savedAlarmSourceNodeId);

            SaveSettings();
        }
        catch (Exception ex)
        {
            _dispatcher.Post(() =>
            {
                ConnectionState = ConnectionState.Disconnected;
                StatusMessage = $"Connection failed: {ex.Message}";
            });
        }
    }

    private bool CanDisconnect()
    {
        return ConnectionState == ConnectionState.Connected
               || ConnectionState == ConnectionState.Reconnecting;
    }

    [RelayCommand(CanExecute = nameof(CanDisconnect))]
    private async Task DisconnectAsync()
    {
        try
        {
            SaveSettings();
            Subscriptions?.Teardown();
            Alarms?.Teardown();
            await _service!.DisconnectAsync();
        }
        catch
        {
            // Best-effort disconnect
        }
        finally
        {
            _dispatcher.Post(() => { ConnectionState = ConnectionState.Disconnected; });
        }
    }

    /// <summary>
    ///     Subscribes all selected tree nodes and switches to the Subscriptions tab.
    /// </summary>
    [RelayCommand]
    private async Task SubscribeSelectedNodesAsync()
    {
        if (SelectedTreeNodes.Count == 0 || !IsConnected) return;

        if (Subscriptions == null) return;

        var nodes = SelectedTreeNodes.ToList();
        foreach (var node in nodes)
            await Subscriptions.AddSubscriptionRecursiveAsync(node.NodeId, node.NodeClass);

        SubscriptionCount = Subscriptions.SubscriptionCount;
        SelectedTabIndex = 1; // Subscriptions tab
    }

    /// <summary>
    ///     Sets the history tab's selected node and switches to the History tab.
    /// </summary>
    [RelayCommand]
    private void ViewHistoryForSelectedNode()
    {
        if (SelectedTreeNodes.Count == 0 || !IsConnected) return;

        var node = SelectedTreeNodes[0];
        if (History != null) History.SelectedNodeId = node.NodeId;
        SelectedTabIndex = 3; // History tab
    }

    /// <summary>
    ///     Stops any active alarm subscription, subscribes to alarms on the selected node,
    ///     and switches to the Alarms tab.
    /// </summary>
    [RelayCommand]
    private async Task MonitorAlarmsForSelectedNodeAsync()
    {
        if (SelectedTreeNodes.Count == 0 || !IsConnected || Alarms == null) return;

        var node = SelectedTreeNodes[0];

        // Stop existing alarm subscription if active
        if (Alarms.IsSubscribed)
        {
            try { await _service!.UnsubscribeAlarmsAsync(); }
            catch { /* best effort */ }
            Alarms.Clear();
        }

        // Subscribe to the selected node
        Alarms.MonitoredNodeIdText = node.NodeId;
        await Alarms.SubscribeCommand.ExecuteAsync(null);

        SelectedTabIndex = 2; // Alarms tab
    }

    /// <summary>
    ///     Updates whether "View History" should be enabled based on the selected node's type.
    ///     Only Variable nodes can have history.
    /// </summary>
    public void UpdateHistoryEnabledForSelection()
    {
        IsHistoryEnabledForSelection = IsConnected
                                       && SelectedTreeNodes.Count > 0
                                       && SelectedTreeNodes[0].NodeClass == "Variable";
    }

    private void LoadSettings()
    {
        var s = _settingsService.Load();
        EndpointUrl = s.EndpointUrl;
        Username = s.Username;
        // Password is intentionally not persisted (security: re-prompt each launch)
        SelectedSecurityMode = s.SecurityMode;
        FailoverUrls = s.FailoverUrls;
        SessionTimeoutSeconds = s.SessionTimeoutSeconds;
        AutoAcceptCertificates = s.AutoAcceptCertificates;
        if (!string.IsNullOrEmpty(s.CertificateStorePath))
            CertificateStorePath = s.CertificateStorePath;
        _savedSubscribedNodes = s.SubscribedNodes;
        _savedAlarmSourceNodeId = s.AlarmSourceNodeId;
    }

    /// <summary>
    /// Persists the current connection, subscription, and alarm-monitoring settings for the next UI session.
    /// </summary>
    public void SaveSettings()
    {
        _settingsService.Save(new UserSettings
        {
            EndpointUrl = EndpointUrl,
            Username = Username,
            // Password is intentionally not persisted (security: re-prompt each launch)
            SecurityMode = SelectedSecurityMode,
            FailoverUrls = FailoverUrls,
            SessionTimeoutSeconds = SessionTimeoutSeconds,
            AutoAcceptCertificates = AutoAcceptCertificates,
            CertificateStorePath = CertificateStorePath,
            SubscribedNodes = Subscriptions?.GetSubscribedNodeIds() ?? _savedSubscribedNodes,
            AlarmSourceNodeId = Alarms?.GetAlarmSourceNodeId() ?? _savedAlarmSourceNodeId
        });
    }

    /// <summary>
    /// Detaches the connection-state handler and disposes the OPC UA client service, releasing
    /// the session, certificate validator, and any background reconnect resources.
    /// </summary>
    public void Dispose()
    {
        if (_service != null)
        {
            _service.ConnectionStateChanged -= OnConnectionStateChanged;
            Subscriptions?.Teardown();
            Alarms?.Teardown();
            _service.Dispose();
        }
    }

    private static string[]? ParseFailoverUrls(string? csv)
    {
        if (string.IsNullOrWhiteSpace(csv))
            return null;

        return csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Where(u => !string.IsNullOrEmpty(u))
            .ToArray();
    }
}
