using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Services;
using SharepointToolbox.Services.Export;
using SkiaSharp;

namespace SharepointToolbox.ViewModels.Tabs;

public partial class StorageViewModel : FeatureViewModelBase
{
    private readonly IStorageService _storageService;
    private readonly ISessionManager _sessionManager;
    private readonly StorageCsvExportService _csvExportService;
    private readonly StorageHtmlExportService _htmlExportService;
    private readonly IBrandingService? _brandingService;
    private readonly ILogger<FeatureViewModelBase> _logger;
    private TenantProfile? _currentProfile;

    [ObservableProperty]
    private bool _perLibrary = true;

    [ObservableProperty]
    private bool _includeSubsites;

    [ObservableProperty]
    private int _folderDepth;

    [ObservableProperty]
    private bool _isDonutChart = true;

    private ObservableCollection<FileTypeMetric> _fileTypeMetrics = new();
    public ObservableCollection<FileTypeMetric> FileTypeMetrics
    {
        get => _fileTypeMetrics;
        private set
        {
            _fileTypeMetrics = value;
            OnPropertyChanged();
            UpdateChartSeries();
        }
    }

    public bool HasChartData => FileTypeMetrics.Count > 0;

    private IEnumerable<ISeries> _pieChartSeries = Enumerable.Empty<ISeries>();
    public IEnumerable<ISeries> PieChartSeries
    {
        get => _pieChartSeries;
        private set { _pieChartSeries = value; OnPropertyChanged(); }
    }

    private IEnumerable<ISeries> _barChartSeries = Enumerable.Empty<ISeries>();
    public IEnumerable<ISeries> BarChartSeries
    {
        get => _barChartSeries;
        private set { _barChartSeries = value; OnPropertyChanged(); }
    }

    private Axis[] _barXAxes = Array.Empty<Axis>();
    public Axis[] BarXAxes
    {
        get => _barXAxes;
        private set { _barXAxes = value; OnPropertyChanged(); }
    }

    private Axis[] _barYAxes = Array.Empty<Axis>();
    public Axis[] BarYAxes
    {
        get => _barYAxes;
        private set { _barYAxes = value; OnPropertyChanged(); }
    }

    public bool IsMaxDepth
    {
        get => FolderDepth >= 999;
        set
        {
            if (value) FolderDepth = 999;
            else if (FolderDepth >= 999) FolderDepth = 0;
            OnPropertyChanged();
        }
    }

    private ObservableCollection<StorageNode> _results = new();
    public ObservableCollection<StorageNode> Results
    {
        get => _results;
        private set
        {
            _results = value;
            OnPropertyChanged();
            NotifySummaryProperties();
            ExportCsvCommand.NotifyCanExecuteChanged();
            ExportHtmlCommand.NotifyCanExecuteChanged();
        }
    }

    // ── Summary properties (computed from root-level library nodes) ─────────

    /// <summary>Sum of TotalSizeBytes across root-level library nodes.</summary>
    public long SummaryTotalSize => Results.Where(n => n.IndentLevel == 0).Sum(n => n.TotalSizeBytes);

    /// <summary>Sum of VersionSizeBytes across root-level library nodes.</summary>
    public long SummaryVersionSize => Results.Where(n => n.IndentLevel == 0).Sum(n => n.VersionSizeBytes);

    /// <summary>Sum of TotalFileCount across root-level library nodes.</summary>
    public long SummaryFileCount => Results.Where(n => n.IndentLevel == 0).Sum(n => n.TotalFileCount);

    public bool HasResults => Results.Count > 0;

    private void NotifySummaryProperties()
    {
        OnPropertyChanged(nameof(SummaryTotalSize));
        OnPropertyChanged(nameof(SummaryVersionSize));
        OnPropertyChanged(nameof(SummaryFileCount));
        OnPropertyChanged(nameof(HasResults));
    }

    public IAsyncRelayCommand ExportCsvCommand { get; }
    public IAsyncRelayCommand ExportHtmlCommand { get; }

    public TenantProfile? CurrentProfile => _currentProfile;

    public StorageViewModel(
        IStorageService storageService,
        ISessionManager sessionManager,
        StorageCsvExportService csvExportService,
        StorageHtmlExportService htmlExportService,
        IBrandingService brandingService,
        ILogger<FeatureViewModelBase> logger)
        : base(logger)
    {
        _storageService    = storageService;
        _sessionManager    = sessionManager;
        _csvExportService  = csvExportService;
        _htmlExportService = htmlExportService;
        _brandingService   = brandingService;
        _logger            = logger;

        ExportCsvCommand  = new AsyncRelayCommand(ExportCsvAsync,  CanExport);
        ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
    }

    /// <summary>Test constructor — omits export services.</summary>
    internal StorageViewModel(
        IStorageService storageService,
        ISessionManager sessionManager,
        ILogger<FeatureViewModelBase> logger,
        IBrandingService? brandingService = null)
        : base(logger)
    {
        _storageService    = storageService;
        _sessionManager    = sessionManager;
        _csvExportService  = null!;
        _htmlExportService = null!;
        _brandingService   = brandingService;
        _logger            = logger;

        ExportCsvCommand  = new AsyncRelayCommand(ExportCsvAsync,  CanExport);
        ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
    }

    protected override async Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
    {
        if (_currentProfile == null)
        {
            StatusMessage = "No tenant selected. Please connect to a tenant first.";
            return;
        }

        var urls = GlobalSites.Select(s => s.Url).Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
        if (urls.Count == 0)
        {
            StatusMessage = "Select at least one site from the toolbar.";
            return;
        }

        var nonEmpty = urls;

        var options = new StorageScanOptions(
            PerLibrary: PerLibrary,
            IncludeSubsites: IncludeSubsites,
            FolderDepth: FolderDepth);

        var allNodes = new List<StorageNode>();
        var allTypeMetrics = new List<FileTypeMetric>();

        int i = 0;
        foreach (var url in nonEmpty)
        {
            ct.ThrowIfCancellationRequested();
            i++;
            progress.Report(new OperationProgress(i, nonEmpty.Count, $"Scanning {url}..."));

            var siteProfile = new TenantProfile
            {
                TenantUrl = url.TrimEnd('/'),
                ClientId = _currentProfile.ClientId,
                Name = _currentProfile.Name
            };
            var ctx = await _sessionManager.GetOrCreateContextAsync(siteProfile, ct);

            var nodes = await _storageService.CollectStorageAsync(ctx, options, progress, ct);

            // Backfill any libraries where StorageMetrics returned zeros
            await _storageService.BackfillZeroNodesAsync(ctx, nodes, progress, ct);

            allNodes.AddRange(nodes);

            progress.Report(OperationProgress.Indeterminate($"Scanning file types: {url}..."));
            var typeMetrics = await _storageService.CollectFileTypeMetricsAsync(ctx, progress, ct);
            allTypeMetrics.AddRange(typeMetrics);
        }

        // Flatten tree for DataGrid display
        var flat = new List<StorageNode>();
        foreach (var node in allNodes)
            FlattenNode(node, 0, flat);

        // Merge file-type metrics across sites (same extension -> sum)
        var mergedMetrics = allTypeMetrics
            .GroupBy(m => m.Extension, StringComparer.OrdinalIgnoreCase)
            .Select(g => new FileTypeMetric(g.Key, g.Sum(m => m.TotalSizeBytes), g.Sum(m => m.FileCount)))
            .OrderByDescending(m => m.TotalSizeBytes)
            .ToList();

        if (Application.Current?.Dispatcher is { } dispatcher)
        {
            await dispatcher.InvokeAsync(() =>
            {
                Results = new ObservableCollection<StorageNode>(flat);
                FileTypeMetrics = new ObservableCollection<FileTypeMetric>(mergedMetrics);
            });
        }
        else
        {
            Results = new ObservableCollection<StorageNode>(flat);
            FileTypeMetrics = new ObservableCollection<FileTypeMetric>(mergedMetrics);
        }
    }

    protected override void OnTenantSwitched(TenantProfile profile)
    {
        _currentProfile = profile;
        Results = new ObservableCollection<StorageNode>();
        FileTypeMetrics = new ObservableCollection<FileTypeMetric>();
        OnPropertyChanged(nameof(CurrentProfile));
        ExportCsvCommand.NotifyCanExecuteChanged();
        ExportHtmlCommand.NotifyCanExecuteChanged();
    }

    internal void SetCurrentProfile(TenantProfile profile) => _currentProfile = profile;

    internal Task TestRunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
        => RunOperationAsync(ct, progress);

    private bool CanExport() => Results.Count > 0;

    private async Task ExportCsvAsync()
    {
        if (Results.Count == 0) return;
        var dialog = new SaveFileDialog
        {
            Title = "Export storage metrics to CSV",
            Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*",
            DefaultExt = "csv",
            FileName = "storage_metrics"
        };
        if (dialog.ShowDialog() != true) return;
        try
        {
            await _csvExportService.WriteAsync(Results, FileTypeMetrics, dialog.FileName, CancellationToken.None);
            OpenFile(dialog.FileName);
        }
        catch (Exception ex)
        {
            StatusMessage = $"Export failed: {ex.Message}";
            _logger.LogError(ex, "CSV export failed.");
        }
    }

    private async Task ExportHtmlAsync()
    {
        if (Results.Count == 0) return;
        var dialog = new SaveFileDialog
        {
            Title = "Export storage metrics to HTML",
            Filter = "HTML files (*.html)|*.html|All files (*.*)|*.*",
            DefaultExt = "html",
            FileName = "storage_metrics"
        };
        if (dialog.ShowDialog() != true) return;
        try
        {
            ReportBranding? branding = null;
            if (_brandingService is not null)
            {
                var mspLogo = await _brandingService.GetMspLogoAsync();
                var clientLogo = _currentProfile?.ClientLogo;
                branding = new ReportBranding(mspLogo, clientLogo);
            }

            await _htmlExportService.WriteAsync(Results, FileTypeMetrics, dialog.FileName, CancellationToken.None, branding);
            OpenFile(dialog.FileName);
        }
        catch (Exception ex)
        {
            StatusMessage = $"Export failed: {ex.Message}";
            _logger.LogError(ex, "HTML export failed.");
        }
    }

    partial void OnIsDonutChartChanged(bool value)
    {
        UpdateChartSeries();
    }

    private void UpdateChartSeries()
    {
        var metrics = FileTypeMetrics.ToList();
        OnPropertyChanged(nameof(HasChartData));

        if (metrics.Count == 0)
        {
            PieChartSeries = Enumerable.Empty<ISeries>();
            BarChartSeries = Enumerable.Empty<ISeries>();
            BarXAxes = Array.Empty<Axis>();
            BarYAxes = Array.Empty<Axis>();
            return;
        }

        // Take top 10 by size, aggregate the rest as "Other"
        var top = metrics.Take(10).ToList();
        long otherSize = metrics.Skip(10).Sum(m => m.TotalSizeBytes);
        int otherCount = metrics.Skip(10).Sum(m => m.FileCount);

        var chartItems = new List<FileTypeMetric>(top);
        if (otherSize > 0)
            chartItems.Add(new FileTypeMetric("Other", otherSize, otherCount));

        // Pie/Donut: one PieSeries per slice (LiveCharts2 requires this for per-slice colors).
        // Tooltip only shows the hovered slice because each series has exactly one value.
        double innerRadius = IsDonutChart ? 50 : 0;
        var pieList = new List<ISeries>();
        foreach (var m in chartItems)
        {
            pieList.Add(new PieSeries<double>
            {
                Values = new[] { (double)m.TotalSizeBytes },
                Name = m.DisplayLabel,
                InnerRadius = innerRadius,
                HoverPushout = 8,
                MaxRadialColumnWidth = 60,
                DataLabelsFormatter = _ => m.DisplayLabel,
                ToolTipLabelFormatter = _ =>
                    $"{m.DisplayLabel}: {FormatBytes(m.TotalSizeBytes)} ({m.FileCount} files)",
                IsVisibleAtLegend = true,
            });
        }
        PieChartSeries = pieList;

        // Bar chart series
        BarChartSeries = new ISeries[]
        {
            new ColumnSeries<long>
            {
                Values = chartItems.Select(m => m.TotalSizeBytes).ToArray(),
                Name = "Size",
                DataLabelsFormatter = point =>
                {
                    int idx = (int)point.Index;
                    return idx < chartItems.Count ? FormatBytes(chartItems[idx].TotalSizeBytes) : "";
                }
            }
        };

        BarXAxes = new Axis[]
        {
            new Axis
            {
                Labels = chartItems.Select(m => m.DisplayLabel).ToArray(),
                LabelsRotation = -45
            }
        };

        BarYAxes = new Axis[]
        {
            new Axis
            {
                Labeler = value => FormatBytes((long)value)
            }
        };
    }

    private static string FormatBytes(long bytes)
    {
        if (bytes < 1024) return $"{bytes} B";
        if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
        if (bytes < 1024 * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB";
        return $"{bytes / (1024.0 * 1024 * 1024):F2} GB";
    }

    private static void FlattenNode(StorageNode node, int level, List<StorageNode> result)
    {
        node.IndentLevel = level;
        result.Add(node);
        foreach (var child in node.Children)
            FlattenNode(child, level + 1, result);
    }

    private static void OpenFile(string filePath)
    {
        try { Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true }); }
        catch { /* ignore -- file may open but this is best-effort */ }
    }
}
