using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Opc.Ua;
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>
///     ViewModel for the alarms panel.
/// </summary>
public partial class AlarmsViewModel : ObservableObject
{
    private static readonly ILogger Logger = Log.ForContext<AlarmsViewModel>();

    private readonly IUiDispatcher _dispatcher;
    private readonly IOpcUaClientService _service;

    /// <summary>
    ///     Last user-visible status message — set when an alarm subscribe / unsubscribe / refresh
    ///     operation fails so the shell can surface the diagnostic instead of silently dropping it.
    ///     Genuine failures are distinguished from "feature not supported" (condition refresh).
    /// </summary>
    [ObservableProperty] private string? _statusMessage;

    [ObservableProperty] private int _interval = 1000;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(SubscribeCommand))]
    [NotifyCanExecuteChangedFor(nameof(UnsubscribeCommand))]
    [NotifyCanExecuteChangedFor(nameof(RefreshCommand))]
    private bool _isConnected;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(SubscribeCommand))]
    [NotifyCanExecuteChangedFor(nameof(UnsubscribeCommand))]
    [NotifyCanExecuteChangedFor(nameof(RefreshCommand))]
    private bool _isSubscribed;

    [ObservableProperty] private string? _monitoredNodeIdText;

    [ObservableProperty] private int _activeAlarmCount;

    /// <summary>Initializes a new instance of the AlarmsViewModel class.</summary>
    /// <param name="service">The OPC UA client service.</param>
    /// <param name="dispatcher">The UI dispatcher for thread-safe operations.</param>
    public AlarmsViewModel(IOpcUaClientService service, IUiDispatcher dispatcher)
    {
        _service = service;
        _dispatcher = dispatcher;
        _service.AlarmEvent += OnAlarmEvent;
    }

    /// <summary>Received alarm events.</summary>
    public ObservableCollection<AlarmEventViewModel> AlarmEvents { get; } = [];

    private void OnAlarmEvent(object? sender, AlarmEventArgs e)
    {
        // Only display alarm/condition events (those with a ConditionName), not generic events
        if (string.IsNullOrEmpty(e.ConditionName)) return;

        _dispatcher.Post(() =>
        {
            // Find existing row by source + condition and update it, or add new
            var existing = AlarmEvents.FirstOrDefault(a =>
                a.SourceName == e.SourceName && a.ConditionName == e.ConditionName);

            if (existing != null)
            {
                var index = AlarmEvents.IndexOf(existing);

                if (!e.Retain)
                {
                    // Alarm is no longer retained — remove it with a single collection mutation
                    // rather than Replace + Remove (which would fire two change notifications).
                    AlarmEvents.RemoveAt(index);
                }
                else
                {
                    // Alarm is still retained — update the row in place.
                    AlarmEvents[index] = new AlarmEventViewModel(
                        e.SourceName, e.ConditionName, e.Severity, e.Message,
                        e.Retain, e.ActiveState, e.AckedState, e.Time,
                        e.EventId, e.ConditionNodeId);
                }
            }
            else if (e.Retain)
            {
                AlarmEvents.Add(new AlarmEventViewModel(
                    e.SourceName, e.ConditionName, e.Severity, e.Message,
                    e.Retain, e.ActiveState, e.AckedState, e.Time,
                    e.EventId, e.ConditionNodeId));
            }

            ActiveAlarmCount = AlarmEvents.Count(a => a.ActiveState && !a.AckedState);
        });
    }

    private bool CanSubscribe()
    {
        return IsConnected && !IsSubscribed;
    }

    [RelayCommand(CanExecute = nameof(CanSubscribe))]
    private async Task SubscribeAsync()
    {
        try
        {
            var sourceNodeId = string.IsNullOrWhiteSpace(MonitoredNodeIdText)
                ? null
                : NodeId.Parse(MonitoredNodeIdText);

            await _service.SubscribeAlarmsAsync(sourceNodeId, Interval);
            IsSubscribed = true;
            StatusMessage = null;

            try
            {
                await _service.RequestConditionRefreshAsync();
            }
            catch (Exception refreshEx)
            {
                // Condition refresh is optional on the server side — log at info level and surface
                // a soft notice rather than a hard failure so the operator can tell apart "server
                // does not advertise refresh" from a genuine subscribe failure.
                Logger.Information(refreshEx, "RequestConditionRefresh not supported by server");
                StatusMessage = "Condition refresh not supported by server (subscribed).";
            }
        }
        catch (Exception ex)
        {
            Logger.Warning(ex, "SubscribeAlarms failed for {Source}", MonitoredNodeIdText ?? "(all)");
            StatusMessage = $"Subscribe to alarms failed: {ex.Message}";
        }
    }

    private bool CanUnsubscribe()
    {
        return IsConnected && IsSubscribed;
    }

    [RelayCommand(CanExecute = nameof(CanUnsubscribe))]
    private async Task UnsubscribeAsync()
    {
        try
        {
            await _service.UnsubscribeAlarmsAsync();
            IsSubscribed = false;
            StatusMessage = null;
        }
        catch (Exception ex)
        {
            Logger.Warning(ex, "UnsubscribeAlarms failed");
            StatusMessage = $"Unsubscribe alarms failed: {ex.Message}";
        }
    }

    [RelayCommand(CanExecute = nameof(CanUnsubscribe))]
    private async Task RefreshAsync()
    {
        try
        {
            await _service.RequestConditionRefreshAsync();
            StatusMessage = null;
        }
        catch (Exception ex)
        {
            // Same as the subscribe-time fallback: refresh is server-side optional. Information-
            // level log + soft status so the operator sees why an explicit refresh did nothing.
            Logger.Information(ex, "RequestConditionRefresh not supported by server");
            StatusMessage = "Condition refresh not supported by server.";
        }
    }

    /// <summary>
    ///     Acknowledges an alarm and returns (success, message).
    /// </summary>
    /// <param name="alarm">The alarm event to acknowledge.</param>
    /// <param name="comment">Optional comment for the acknowledgment.</param>
    /// <returns>A tuple with success flag and message.</returns>
    public async Task<(bool Success, string Message)> AcknowledgeAlarmAsync(AlarmEventViewModel alarm, string comment)
    {
        if (!IsConnected || alarm.EventId == null || alarm.ConditionNodeId == null)
            return (false, "Alarm cannot be acknowledged (missing EventId or ConditionId).");

        try
        {
            var result = await _service.AcknowledgeAlarmAsync(alarm.ConditionNodeId, alarm.EventId, comment);
            if (Opc.Ua.StatusCode.IsGood(result))
                return (true, "Alarm acknowledged successfully.");
            return (false, $"Acknowledge failed: {Helpers.StatusCodeFormatter.Format(result)}");
        }
        catch (Exception ex)
        {
            return (false, $"Error: {ex.Message}");
        }
    }

    /// <summary>Shelves / unshelves an alarm and returns (success, message).</summary>
    /// <param name="alarm">The alarm event to shelve or unshelve.</param>
    /// <param name="kind">The shelve operation (OneShot / Timed / Unshelve).</param>
    /// <param name="durationSeconds">The timed-shelve duration in seconds (required for <see cref="ShelveKind.Timed"/>).</param>
    /// <returns>A tuple with success flag and message.</returns>
    public async Task<(bool Success, string Message)> ShelveAlarmAsync(
        AlarmEventViewModel alarm, ShelveKind kind, double durationSeconds)
    {
        if (!IsConnected || alarm.ConditionNodeId == null)
            return (false, "Alarm cannot be shelved (not connected or missing ConditionId).");
        if (kind == ShelveKind.Timed && durationSeconds <= 0)
            return (false, "Timed shelve requires a positive duration (seconds).");

        try
        {
            var result = await _service.ShelveAlarmAsync(alarm.ConditionNodeId, kind, durationSeconds);
            if (Opc.Ua.StatusCode.IsGood(result))
                return (true, kind switch
                {
                    ShelveKind.OneShot => "Alarm shelved (one-shot).",
                    ShelveKind.Timed => $"Alarm shelved for {durationSeconds:0}s.",
                    ShelveKind.Unshelve => "Alarm unshelved.",
                    _ => "Shelve succeeded."
                });
            return (false, $"Shelve failed: {Helpers.StatusCodeFormatter.Format(result)}");
        }
        catch (Exception ex)
        {
            return (false, $"Error: {ex.Message}");
        }
    }

    /// <summary>Confirms an alarm and returns (success, message).</summary>
    /// <param name="alarm">The alarm event to confirm.</param>
    /// <param name="comment">Optional comment for the confirmation.</param>
    /// <returns>A tuple with success flag and message.</returns>
    public async Task<(bool Success, string Message)> ConfirmAlarmAsync(AlarmEventViewModel alarm, string comment)
    {
        if (!IsConnected || alarm.EventId == null || alarm.ConditionNodeId == null)
            return (false, "Alarm cannot be confirmed (missing EventId or ConditionId).");

        try
        {
            var result = await _service.ConfirmAlarmAsync(alarm.ConditionNodeId, alarm.EventId, comment);
            if (Opc.Ua.StatusCode.IsGood(result))
                return (true, "Alarm confirmed successfully.");
            return (false, $"Confirm failed: {Helpers.StatusCodeFormatter.Format(result)}");
        }
        catch (Exception ex)
        {
            return (false, $"Error: {ex.Message}");
        }
    }

    /// <summary>
    ///     Returns the monitored node ID for persistence, or null if not subscribed.
    /// </summary>
    /// <returns>The monitored node ID, or null if not subscribed.</returns>
    public string? GetAlarmSourceNodeId()
    {
        return IsSubscribed ? MonitoredNodeIdText : null;
    }

    /// <summary>
    ///     Restores an alarm subscription and requests a condition refresh.
    /// </summary>
    /// <param name="sourceNodeId">The source node ID to restore the subscription for.</param>
    /// <returns>A task that completes when the restore operation finishes.</returns>
    public async Task RestoreAlarmSubscriptionAsync(string? sourceNodeId)
    {
        if (!IsConnected || string.IsNullOrWhiteSpace(sourceNodeId)) return;

        MonitoredNodeIdText = sourceNodeId;

        try
        {
            var nodeId = string.IsNullOrWhiteSpace(sourceNodeId)
                ? null
                : NodeId.Parse(sourceNodeId);

            await _service.SubscribeAlarmsAsync(nodeId, Interval);
            IsSubscribed = true;
            StatusMessage = null;

            try
            {
                await _service.RequestConditionRefreshAsync();
            }
            catch (Exception refreshEx)
            {
                Logger.Information(refreshEx, "RequestConditionRefresh not supported by server (restore path)");
                StatusMessage = "Condition refresh not supported by server (restored subscription).";
            }
        }
        catch (Exception ex)
        {
            Logger.Warning(ex, "RestoreAlarmSubscription failed for {Source}", sourceNodeId);
            StatusMessage = $"Restore alarm subscription failed: {ex.Message}";
        }
    }

    /// <summary>
    ///     Clears alarm events and resets state.
    /// </summary>
    public void Clear()
    {
        AlarmEvents.Clear();
        IsSubscribed = false;
        ActiveAlarmCount = 0;
    }

    /// <summary>
    ///     Re-hooks event handlers to the service after a server-side reconnect.
    ///     Safe to call when already attached (duplicate += is a no-op in .NET multicast delegates).
    /// </summary>
    public void Reattach()
    {
        _service.AlarmEvent -= OnAlarmEvent;
        _service.AlarmEvent += OnAlarmEvent;
    }

    /// <summary>
    ///     Unhooks event handlers from the service.
    /// </summary>
    public void Teardown()
    {
        _service.AlarmEvent -= OnAlarmEvent;
    }
}