using System.Collections.ObjectModel;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OTSSignsOrchestrator.Core.Data;
using OTSSignsOrchestrator.Core.Models.DTOs;
using OTSSignsOrchestrator.Core.Models.Entities;
using OTSSignsOrchestrator.Core.Services;
using OTSSignsOrchestrator.Desktop.Models;
using OTSSignsOrchestrator.Desktop.Services;

namespace OTSSignsOrchestrator.Desktop.ViewModels;

/// <summary>
/// ViewModel for listing, viewing, and managing CMS instances.
/// All data is fetched live from Docker Swarm hosts — nothing stored locally.
/// Server operations (decommission, suspend, reactivate) go through the REST API.
/// Real-time updates arrive via SignalR → WeakReferenceMessenger.
/// </summary>
public partial class InstancesViewModel : ObservableObject,
    IRecipient<AlertRaisedMessage>,
    IRecipient<InstanceStatusChangedMessage>,
    IRecipient<JobCreatedMessage>,
    IRecipient<JobCompletedMessage>
{
    private readonly IServiceProvider _services;
    private readonly ILogger<InstancesViewModel> _logger;
    private readonly IServerApiClient? _serverApi;

    [ObservableProperty] private ObservableCollection<LiveStackItem> _instances = new();
    [ObservableProperty] private LiveStackItem? _selectedInstance;
    [ObservableProperty] private string _statusMessage = string.Empty;
    [ObservableProperty] private bool _isBusy;
    [ObservableProperty] private string _filterText = string.Empty;
    [ObservableProperty] private ObservableCollection<ServiceInfo> _selectedServices = new();

    // Available SSH hosts — loaded for display and used to scope operations
    [ObservableProperty] private ObservableCollection<SshHost> _availableHosts = new();
    [ObservableProperty] private SshHost? _selectedSshHost;

    // ── P1 Authentik Banner ──────────────────────────────────────────────────
    [ObservableProperty] private bool _isAuthentikP1BannerVisible;
    [ObservableProperty] private string _authentikP1Message = string.Empty;

    // ── Container Logs ──────────────────────────────────────────────────────
    [ObservableProperty] private ObservableCollection<ServiceLogEntry> _logEntries = new();
    [ObservableProperty] private ObservableCollection<string> _logServiceFilter = new();
    [ObservableProperty] private string _selectedLogService = "All Services";
    [ObservableProperty] private bool _isLogsPanelVisible;
    [ObservableProperty] private bool _isLogsAutoRefresh = true;
    [ObservableProperty] private bool _isLoadingLogs;
    [ObservableProperty] private string _logsStatusMessage = string.Empty;
    [ObservableProperty] private int _logTailLines = 200;

    private DispatcherTimer? _logRefreshTimer;
    private bool _isLogRefreshRunning;

    /// <summary>Raised when the instance details modal should be opened for the given ViewModel.</summary>
    public event Action<InstanceDetailsViewModel>? OpenDetailsRequested;

    /// <summary>
    /// Callback the View wires up to show a confirmation dialog.
    /// Parameters: (title, message) → returns true if the user confirmed.
    /// </summary>
    public Func<string, string, Task<bool>>? ConfirmAsync { get; set; }

    /// <summary>
    /// Callback the View wires up to show a multi-step confirmation dialog for decommission.
    /// Parameters: (abbreviation) → returns true if confirmed through all steps.
    /// </summary>
    public Func<string, Task<bool>>? ConfirmDecommissionAsync { get; set; }

    private string? _pendingSelectAbbrev;

    public InstancesViewModel(IServiceProvider services)
    {
        _services = services;
        _logger = services.GetRequiredService<ILogger<InstancesViewModel>>();
        _serverApi = services.GetService<IServerApiClient>();

        // Register for SignalR messages via WeakReferenceMessenger
        WeakReferenceMessenger.Default.Register<AlertRaisedMessage>(this);
        WeakReferenceMessenger.Default.Register<InstanceStatusChangedMessage>(this);
        WeakReferenceMessenger.Default.Register<JobCreatedMessage>(this);
        WeakReferenceMessenger.Default.Register<JobCompletedMessage>(this);

        _ = RefreshAllAsync();
    }

    /// <summary>
    /// Queues an abbreviation to be auto-selected once the next live refresh completes.
    /// Call immediately after construction (before <see cref="RefreshAllAsync"/> finishes).
    /// </summary>
    public void SetPendingSelection(string abbrev)
        => _pendingSelectAbbrev = abbrev;

    // ── SignalR Message Handlers ─────────────────────────────────────────────
    // These are called on the UI thread (SignalR handlers dispatch via Dispatcher.UIThread).

    void IRecipient<AlertRaisedMessage>.Receive(AlertRaisedMessage message)
    {
        var (severity, msg) = message.Value;
        if (string.Equals(severity, "Critical", StringComparison.OrdinalIgnoreCase) &&
            msg.Contains("Authentik", StringComparison.OrdinalIgnoreCase))
        {
            AuthentikP1Message = msg;
            IsAuthentikP1BannerVisible = true;
        }
    }

    void IRecipient<InstanceStatusChangedMessage>.Receive(InstanceStatusChangedMessage message)
    {
        var (customerId, status) = message.Value;
        _logger.LogInformation("Instance status changed: customer={CustomerId} status={Status}", customerId, status);
        StatusMessage = $"Instance {customerId} status → {status}";
        // Refresh the list to pick up the new status
        _ = RefreshAllAsync();
    }

    void IRecipient<JobCreatedMessage>.Receive(JobCreatedMessage message)
    {
        var (jobId, abbrev, jobType) = message.Value;
        _logger.LogInformation("Job created: {JobId} type={JobType} abbrev={Abbrev}", jobId, jobType, abbrev);
        StatusMessage = $"Job '{jobType}' created for {abbrev} (id: {jobId[..8]}…)";
    }

    void IRecipient<JobCompletedMessage>.Receive(JobCompletedMessage message)
    {
        var (jobId, success, summary) = message.Value;
        _logger.LogInformation("Job completed: {JobId} success={Success} summary={Summary}", jobId, success, summary);
        StatusMessage = success
            ? $"Job {jobId[..8]}… completed: {summary}"
            : $"Job {jobId[..8]}… failed: {summary}";
        // Refresh the instance list to reflect changes from the completed job
        _ = RefreshAllAsync();
    }

    // ── Load / Refresh ──────────────────────────────────────────────────────

    /// <summary>
    /// Enumerates all SSH hosts, then calls docker stack ls on each to build the
    /// live instance list. Only stacks matching *-cms-stack are shown.
    /// </summary>
    [RelayCommand]
    private async Task LoadInstancesAsync() => await RefreshAllAsync();

    private async Task RefreshAllAsync()
    {
        IsBusy = true;
        StatusMessage = "Loading live instances from all hosts...";
        SelectedServices = new ObservableCollection<ServiceInfo>();
        try
        {
            using var scope = _services.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService<XiboContext>();
            var hosts = await db.SshHosts.OrderBy(h => h.Label).ToListAsync();
            AvailableHosts = new ObservableCollection<SshHost>(hosts);

            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            var all = new List<LiveStackItem>();
            var errors = new List<string>();

            foreach (var host in hosts)
            {
                try
                {
                    dockerCli.SetHost(host);
                    var stacks = await dockerCli.ListStacksAsync();
                    foreach (var stack in stacks.Where(s => s.Name.EndsWith("-cms-stack")))
                    {
                        all.Add(new LiveStackItem
                        {
                            StackName      = stack.Name,
                            CustomerAbbrev = stack.Name[..^10],
                            ServiceCount   = stack.ServiceCount,
                            Host           = host,
                        });
                    }
                }
                catch (Exception ex) { errors.Add($"{host.Label}: {ex.Message}"); }
            }

            // Enrich with server-side customer IDs if the server API is available
            if (_serverApi is not null)
            {
                try
                {
                    var fleet = await _serverApi.GetFleetAsync();
                    var lookup = fleet.ToDictionary(f => f.Abbreviation, f => f.CustomerId, StringComparer.OrdinalIgnoreCase);
                    foreach (var item in all)
                    {
                        if (lookup.TryGetValue(item.CustomerAbbrev, out var customerId))
                            item.CustomerId = customerId;
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Could not enrich instances with server fleet data");
                }
            }

            if (!string.IsNullOrWhiteSpace(FilterText))
                all = all.Where(i =>
                    i.StackName.Contains(FilterText, StringComparison.OrdinalIgnoreCase) ||
                    i.CustomerAbbrev.Contains(FilterText, StringComparison.OrdinalIgnoreCase) ||
                    i.HostLabel.Contains(FilterText, StringComparison.OrdinalIgnoreCase)).ToList();

            Instances = new ObservableCollection<LiveStackItem>(all);

            // Auto-select a pending instance (e.g. just deployed from Create Instance page)
            if (_pendingSelectAbbrev is not null)
            {
                SelectedInstance = all.FirstOrDefault(i =>
                    i.CustomerAbbrev.Equals(_pendingSelectAbbrev, StringComparison.OrdinalIgnoreCase));
                _pendingSelectAbbrev = null;
            }

            var msg = $"Found {all.Count} instance(s) across {hosts.Count} host(s).";
            if (errors.Count > 0) msg += $" | Errors: {string.Join(" | ", errors)}";
            StatusMessage = msg;
        }
        catch (Exception ex) { StatusMessage = $"Error: {ex.Message}"; }
        finally { IsBusy = false; }
    }

    [RelayCommand]
    private async Task InspectInstanceAsync()
    {
        if (SelectedInstance == null) return;
        IsBusy = true;
        StatusMessage = $"Inspecting '{SelectedInstance.StackName}'...";
        try
        {
            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            dockerCli.SetHost(SelectedInstance.Host);
            var services = await dockerCli.InspectStackServicesAsync(SelectedInstance.StackName);
            SelectedServices = new ObservableCollection<ServiceInfo>(services);
            StatusMessage = $"Found {services.Count} service(s) in stack '{SelectedInstance.StackName}'.";

            // Populate service filter dropdown and show logs panel
            var filterItems = new List<string> { "All Services" };
            filterItems.AddRange(services.Select(s => s.Name));
            LogServiceFilter = new ObservableCollection<string>(filterItems);
            SelectedLogService = "All Services";
            IsLogsPanelVisible = true;

            // Fetch initial logs and start auto-refresh
            await FetchLogsInternalAsync();
            StartLogAutoRefresh();
        }
        catch (Exception ex) { StatusMessage = $"Error inspecting: {ex.Message}"; }
        finally { IsBusy = false; }
    }

    // ── Container Log Commands ──────────────────────────────────────────────

    [RelayCommand]
    private async Task RefreshLogsAsync()
    {
        await FetchLogsInternalAsync();
    }

    [RelayCommand]
    private void ToggleLogsAutoRefresh()
    {
        IsLogsAutoRefresh = !IsLogsAutoRefresh;
        if (IsLogsAutoRefresh)
            StartLogAutoRefresh();
        else
            StopLogAutoRefresh();
    }

    [RelayCommand]
    private void CloseLogsPanel()
    {
        StopLogAutoRefresh();
        IsLogsPanelVisible = false;
        LogEntries = new ObservableCollection<ServiceLogEntry>();
        LogsStatusMessage = string.Empty;
    }

    partial void OnSelectedLogServiceChanged(string value)
    {
        // When user changes the service filter, refresh logs immediately
        if (IsLogsPanelVisible)
            _ = FetchLogsInternalAsync();
    }

    private async Task FetchLogsInternalAsync()
    {
        if (SelectedInstance == null || _isLogRefreshRunning) return;

        _isLogRefreshRunning = true;
        IsLoadingLogs = true;
        try
        {
            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            dockerCli.SetHost(SelectedInstance.Host);

            string? serviceFilter = SelectedLogService == "All Services" ? null : SelectedLogService;
            var entries = await dockerCli.GetServiceLogsAsync(
                SelectedInstance.StackName, serviceFilter, LogTailLines);

            LogEntries = new ObservableCollection<ServiceLogEntry>(entries);
            LogsStatusMessage = $"{entries.Count} log line(s) · last fetched {DateTime.Now:HH:mm:ss}";
        }
        catch (Exception ex)
        {
            LogsStatusMessage = $"Error fetching logs: {ex.Message}";
        }
        finally
        {
            IsLoadingLogs = false;
            _isLogRefreshRunning = false;
        }
    }

    private void StartLogAutoRefresh()
    {
        StopLogAutoRefresh();
        if (!IsLogsAutoRefresh) return;

        _logRefreshTimer = new DispatcherTimer
        {
            Interval = TimeSpan.FromSeconds(5)
        };
        _logRefreshTimer.Tick += async (_, _) =>
        {
            if (IsLogsPanelVisible && IsLogsAutoRefresh && !_isLogRefreshRunning)
                await FetchLogsInternalAsync();
        };
        _logRefreshTimer.Start();
    }

    private void StopLogAutoRefresh()
    {
        _logRefreshTimer?.Stop();
        _logRefreshTimer = null;
    }

    // ── Restart Commands ────────────────────────────────────────────────

    [RelayCommand]
    private async Task RestartStackAsync()
    {
        if (SelectedInstance == null) return;

        if (ConfirmAsync is not null)
        {
            var confirmed = await ConfirmAsync(
                "Restart Stack",
                $"Are you sure you want to restart all services in '{SelectedInstance.StackName}'?\n\nThis will force-update every service in the stack, causing brief downtime.");
            if (!confirmed) return;
        }

        IsBusy = true;
        StatusMessage = $"Restarting all services in '{SelectedInstance.StackName}'...";
        try
        {
            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            dockerCli.SetHost(SelectedInstance.Host);

            var services = await dockerCli.InspectStackServicesAsync(SelectedInstance.StackName);
            var failures = new List<string>();

            for (var i = 0; i < services.Count; i++)
            {
                var svc = services[i];
                StatusMessage = $"Restarting service {i + 1}/{services.Count}: {svc.Name}...";
                var ok = await dockerCli.ForceUpdateServiceAsync(svc.Name);
                if (!ok) failures.Add(svc.Name);
            }

            StatusMessage = failures.Count == 0
                ? $"All {services.Count} service(s) in '{SelectedInstance.StackName}' restarted successfully."
                : $"Restarted with errors — failed services: {string.Join(", ", failures)}";
        }
        catch (Exception ex) { StatusMessage = $"Error restarting stack: {ex.Message}"; }
        finally { IsBusy = false; }
    }

    [RelayCommand]
    private async Task RestartServiceAsync(ServiceInfo? service)
    {
        if (service is null || SelectedInstance is null) return;

        if (ConfirmAsync is not null)
        {
            var confirmed = await ConfirmAsync(
                "Restart Service",
                $"Are you sure you want to restart '{service.Name}'?\n\nThis will force-update the service, causing its tasks to be recreated.");
            if (!confirmed) return;
        }

        IsBusy = true;
        StatusMessage = $"Restarting service '{service.Name}'...";
        try
        {
            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            dockerCli.SetHost(SelectedInstance.Host);
            var ok = await dockerCli.ForceUpdateServiceAsync(service.Name);
            StatusMessage = ok
                ? $"Service '{service.Name}' restarted successfully."
                : $"Failed to restart service '{service.Name}'.";

            // Refresh services to show updated replica status
            var services = await dockerCli.InspectStackServicesAsync(SelectedInstance.StackName);
            SelectedServices = new ObservableCollection<ServiceInfo>(services);
        }
        catch (Exception ex) { StatusMessage = $"Error restarting service: {ex.Message}"; }
        finally { IsBusy = false; }
    }

    [RelayCommand]
    private async Task DeleteInstanceAsync()
    {
        if (SelectedInstance == null) return;
        IsBusy = true;
        StatusMessage = $"Deleting {SelectedInstance.StackName}...";
        try
        {
            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            dockerCli.SetHost(SelectedInstance.Host);
            var dockerSecrets = _services.GetRequiredService<SshDockerSecretsService>();
            dockerSecrets.SetHost(SelectedInstance.Host);
            using var scope = _services.CreateScope();
            var instanceSvc = scope.ServiceProvider.GetRequiredService<InstanceService>();
            var result = await instanceSvc.DeleteInstanceAsync(
                SelectedInstance.StackName, SelectedInstance.CustomerAbbrev);
            StatusMessage = result.Success
                ? $"Instance '{SelectedInstance.StackName}' deleted."
                : $"Delete failed: {result.ErrorMessage}";
            await RefreshAllAsync();
        }
        catch (Exception ex) { StatusMessage = $"Error deleting: {ex.Message}"; }
        finally { IsBusy = false; }
    }

    [RelayCommand]
    private async Task RotateMySqlPasswordAsync()
    {
        if (SelectedInstance == null) return;
        IsBusy = true;
        StatusMessage = $"Rotating MySQL password for {SelectedInstance.StackName}...";
        try
        {
            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            dockerCli.SetHost(SelectedInstance.Host);
            var dockerSecrets = _services.GetRequiredService<SshDockerSecretsService>();
            dockerSecrets.SetHost(SelectedInstance.Host);
            using var scope = _services.CreateScope();
            var instanceSvc = scope.ServiceProvider.GetRequiredService<InstanceService>();
            var (ok, msg) = await instanceSvc.RotateMySqlPasswordAsync(SelectedInstance.StackName);
            StatusMessage = ok ? $"Done: {msg}" : $"Failed: {msg}";
            await RefreshAllAsync();
        }
        catch (Exception ex) { StatusMessage = $"Error rotating password: {ex.Message}"; }
        finally { IsBusy = false; }
    }

    // ── P1 Banner Commands ────────────────────────────────────────────────

    [RelayCommand]
    private void DismissP1Banner()
    {
        IsAuthentikP1BannerVisible = false;
        AuthentikP1Message = string.Empty;
    }

    /// <summary>
    /// Called from a SignalR <c>AlertRaised</c> handler (runs on a background thread).
    /// CRITICAL: wraps all property updates with <see cref="Dispatcher.UIThread"/> to
    /// avoid silent cross-thread exceptions in Avalonia.
    /// </summary>
    public void HandleAlertRaised(string severity, string message)
    {
        if (string.Equals(severity, "Critical", StringComparison.OrdinalIgnoreCase) &&
            message.Contains("Authentik", StringComparison.OrdinalIgnoreCase))
        {
            Dispatcher.UIThread.InvokeAsync(() =>
            {
                AuthentikP1Message = message;
                IsAuthentikP1BannerVisible = true;
            });
        }
    }

    // ── Server-side Job Commands (decommission, suspend, reactivate) ────────
    // Desktop has NO direct infrastructure access — all operations go through the server REST API.

    [RelayCommand]
    private async Task DecommissionAsync(LiveStackItem? instance)
    {
        instance ??= SelectedInstance;
        if (instance is null) return;

        if (instance.CustomerId is null)
        {
            StatusMessage = "Cannot decommission: no server-side customer ID available for this instance.";
            return;
        }

        // Multi-step confirmation: user must type the abbreviation to confirm
        if (ConfirmDecommissionAsync is not null)
        {
            var confirmed = await ConfirmDecommissionAsync(instance.CustomerAbbrev);
            if (!confirmed) return;
        }
        else if (ConfirmAsync is not null)
        {
            var confirmed = await ConfirmAsync(
                "Decommission Instance",
                $"Are you sure you want to decommission '{instance.CustomerAbbrev}'?\n\n" +
                "This will:\n" +
                "  • Remove all Docker services and stack\n" +
                "  • Delete Docker secrets\n" +
                "  • Remove NFS volumes and data\n" +
                "  • Revoke Authentik provider\n" +
                "  • Mark the customer as decommissioned\n\n" +
                "This action is IRREVERSIBLE.");
            if (!confirmed) return;
        }

        await CreateServerJobAsync(instance, "decommission");
    }

    [RelayCommand]
    private async Task SuspendInstanceAsync(LiveStackItem? instance)
    {
        instance ??= SelectedInstance;
        if (instance is null) return;

        if (instance.CustomerId is null)
        {
            StatusMessage = "Cannot suspend: no server-side customer ID available for this instance.";
            return;
        }

        if (ConfirmAsync is not null)
        {
            var confirmed = await ConfirmAsync(
                "Suspend Instance",
                $"Are you sure you want to suspend '{instance.CustomerAbbrev}'?\n\n" +
                "The instance will be scaled to zero replicas. Data will be preserved.");
            if (!confirmed) return;
        }

        await CreateServerJobAsync(instance, "suspend");
    }

    [RelayCommand]
    private async Task ReactivateInstanceAsync(LiveStackItem? instance)
    {
        instance ??= SelectedInstance;
        if (instance is null) return;

        if (instance.CustomerId is null)
        {
            StatusMessage = "Cannot reactivate: no server-side customer ID available for this instance.";
            return;
        }

        await CreateServerJobAsync(instance, "reactivate");
    }

    private async Task CreateServerJobAsync(LiveStackItem instance, string jobType)
    {
        if (_serverApi is null)
        {
            StatusMessage = "Server API client is not configured.";
            return;
        }

        IsBusy = true;
        StatusMessage = $"Requesting '{jobType}' for {instance.CustomerAbbrev}...";
        try
        {
            var response = await _serverApi.CreateJobAsync(
                new CreateJobRequest(instance.CustomerId!.Value, jobType, null));
            StatusMessage = $"Job '{jobType}' created (id: {response.Id.ToString()[..8]}…). Status: {response.Status}";
            _logger.LogInformation("Server job created: {JobId} type={JobType} customer={CustomerId}",
                response.Id, jobType, instance.CustomerId);
        }
        catch (Refit.ApiException ex)
        {
            StatusMessage = $"Server error creating '{jobType}' job: {ex.StatusCode} — {ex.Content}";
            _logger.LogError(ex, "Failed to create {JobType} job for customer {CustomerId}", jobType, instance.CustomerId);
        }
        catch (Exception ex)
        {
            StatusMessage = $"Error creating '{jobType}' job: {ex.Message}";
            _logger.LogError(ex, "Failed to create {JobType} job for customer {CustomerId}", jobType, instance.CustomerId);
        }
        finally { IsBusy = false; }
    }

    // ── Details ─────────────────────────────────────────────────────────────

    [RelayCommand]
    private async Task OpenDetailsAsync()
    {
        if (SelectedInstance == null) return;

        IsBusy = true;
        StatusMessage = $"Loading details for '{SelectedInstance.StackName}'...";
        try
        {
            // Set the SSH host on singleton Docker services so modal operations target the right host
            var dockerCli = _services.GetRequiredService<SshDockerCliService>();
            dockerCli.SetHost(SelectedInstance.Host);
            var dockerSecrets = _services.GetRequiredService<SshDockerSecretsService>();
            dockerSecrets.SetHost(SelectedInstance.Host);

            var detailsVm = _services.GetRequiredService<InstanceDetailsViewModel>();
            await detailsVm.LoadAsync(SelectedInstance);

            OpenDetailsRequested?.Invoke(detailsVm);
            StatusMessage = string.Empty;
        }
        catch (Exception ex) { StatusMessage = $"Error opening details: {ex.Message}"; }
        finally { IsBusy = false; }
    }
}
