using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using SharepointToolbox.Core.Messages;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Localization;
using SharepointToolbox.Services;
using SharepointToolbox.Core.Helpers;
using SharepointToolbox.Services.Export;

namespace SharepointToolbox.ViewModels.Tabs;

/// <summary>
/// ViewModel for the Permissions tab.
/// Orchestrates permission scanning across one or multiple SharePoint sites
/// and exports results to CSV or HTML.
/// </summary>
public partial class PermissionsViewModel : FeatureViewModelBase
{
    private readonly IPermissionsService _permissionsService;
    private readonly ISiteListService _siteListService;
    private readonly ISessionManager _sessionManager;
    private readonly CsvExportService? _csvExportService;
    private readonly HtmlExportService? _htmlExportService;
    private readonly IBrandingService? _brandingService;
    private readonly ISharePointGroupResolver? _groupResolver;
    private readonly ILogger<FeatureViewModelBase> _logger;
    private readonly SettingsService? _settingsService;
    private readonly IOwnershipElevationService? _ownershipService;

    // ── Observable properties ───────────────────────────────────────────────

    [ObservableProperty]
    private bool _includeInherited;

    [ObservableProperty]
    private bool _scanFolders = true;

    /// <summary>Placeholder for the Export Options toggle — no-op in PermissionsViewModel (reserved for future use).</summary>
    [ObservableProperty]
    private bool _mergePermissions;

    /// <summary>
    /// When true, the HTML report hides the raw "SharePoint Group: SharingLinks.{guid}…"
    /// / "Limited Access System Group For Web|List {guid}" text for rows where the
    /// system-group target was resolved — only the link-type badge and target link are shown.
    /// Rows whose target could not be resolved still display the raw name as a fallback.
    /// </summary>
    [ObservableProperty]
    private bool _hideSystemGroupRaw = true;

    /// <summary>When true, sharing link entries (SharingLinkType != null) are removed from results and exports.</summary>
    [ObservableProperty]
    private bool _excludeSharingLinks;

    /// <summary>When true, "Limited Access System Group For Web/List" entries are removed from results and exports.</summary>
    [ObservableProperty]
    private bool _excludeSystemGroups;

    [ObservableProperty]
    private bool _includeSubsites;

    [ObservableProperty]
    private int _folderDepth = 1;

    /// <summary>
    /// When true, sets FolderDepth to 999 (scan all levels).
    /// </summary>
    public bool IsMaxDepth
    {
        get => FolderDepth >= 999;
        set
        {
            if (value)
                FolderDepth = 999;
            else if (FolderDepth >= 999)
                FolderDepth = 1;
            OnPropertyChanged();
        }
    }

    [ObservableProperty]
    private ObservableCollection<PermissionEntry> _results = new();

    /// <summary>
    /// When true, displays simplified plain-language labels instead of raw SharePoint role names.
    /// Toggling does not re-run the scan.
    /// </summary>
    [ObservableProperty]
    private bool _isSimplifiedMode;

    /// <summary>
    /// When true, shows individual item-level rows (detailed view).
    /// When false, shows only summary rows grouped by risk level (simple view).
    /// Only meaningful when IsSimplifiedMode is true.
    /// </summary>
    [ObservableProperty]
    private bool _isDetailView = true;

    /// <summary>0 = Single file, 1 = Split by site.</summary>
    [ObservableProperty] private int _splitModeIndex;
    /// <summary>0 = Separate HTML files, 1 = Single tabbed HTML.</summary>
    [ObservableProperty] private int _htmlLayoutIndex;

    private ReportSplitMode CurrentSplit => SplitModeIndex == 1 ? ReportSplitMode.BySite : ReportSplitMode.Single;
    private HtmlSplitLayout CurrentLayout => HtmlLayoutIndex == 1 ? HtmlSplitLayout.SingleTabbed : HtmlSplitLayout.SeparateFiles;

    /// <summary>
    /// Results after applying ExcludeSharingLinks / ExcludeSystemGroups filters.
    /// Rebuilt when Results changes or filter flags change.
    /// </summary>
    private IReadOnlyList<PermissionEntry> _filteredResults = Array.Empty<PermissionEntry>();
    public IReadOnlyList<PermissionEntry> FilteredResults
    {
        get => _filteredResults;
        private set => SetProperty(ref _filteredResults, value);
    }

    /// <summary>
    /// Simplified wrappers computed from Results. Rebuilt when Results changes.
    /// </summary>
    private IReadOnlyList<SimplifiedPermissionEntry> _simplifiedResults = Array.Empty<SimplifiedPermissionEntry>();
    public IReadOnlyList<SimplifiedPermissionEntry> SimplifiedResults
    {
        get => _simplifiedResults;
        private set => SetProperty(ref _simplifiedResults, value);
    }

    /// <summary>
    /// Summary counts grouped by risk level. Rebuilt when SimplifiedResults changes.
    /// </summary>
    private IReadOnlyList<PermissionSummary> _summaries = Array.Empty<PermissionSummary>();
    public IReadOnlyList<PermissionSummary> Summaries
    {
        get => _summaries;
        private set => SetProperty(ref _summaries, value);
    }

    /// <summary>
    /// The collection the DataGrid actually binds to. Returns:
    /// - FilteredResults (raw) when simplified mode is OFF
    /// - SimplifiedResults when simplified mode is ON and detail view is ON
    /// - (View handles summary display separately via Summaries property)
    /// </summary>
    public object ActiveItemsSource => IsSimplifiedMode
        ? (object)SimplifiedResults
        : FilteredResults;

    partial void OnFolderDepthChanged(int value) => OnPropertyChanged(nameof(IsMaxDepth));

    partial void OnExcludeSharingLinksChanged(bool value) => RefreshAfterFilterChange();
    partial void OnExcludeSystemGroupsChanged(bool value) => RefreshAfterFilterChange();

    private void RefreshAfterFilterChange()
    {
        if (Results.Count == 0) return;
        RebuildFilteredResults();
        if (IsSimplifiedMode) RebuildSimplifiedData();
        OnPropertyChanged(nameof(ActiveItemsSource));
    }

    private void RebuildFilteredResults()
    {
        IEnumerable<PermissionEntry> filtered = Results;
        if (ExcludeSharingLinks)
            filtered = filtered.Where(e => string.IsNullOrEmpty(e.SharingLinkType));
        if (ExcludeSystemGroups)
            filtered = filtered.Where(e => !e.GrantedThrough.Contains("Limited Access System Group", StringComparison.OrdinalIgnoreCase));
        FilteredResults = filtered.ToList();
    }

    // ── Commands ────────────────────────────────────────────────────────────

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

    // ── Current tenant profile (received via WeakReferenceMessenger) ────────

    internal TenantProfile? _currentProfile;

    // ── Constructors ────────────────────────────────────────────────────────

    /// <summary>
    /// Full constructor — used by DI and production code.
    /// </summary>
    public PermissionsViewModel(
        IPermissionsService permissionsService,
        ISiteListService siteListService,
        ISessionManager sessionManager,
        CsvExportService csvExportService,
        HtmlExportService htmlExportService,
        IBrandingService brandingService,
        ILogger<FeatureViewModelBase> logger,
        ISharePointGroupResolver? groupResolver = null,
        SettingsService? settingsService = null,
        IOwnershipElevationService? ownershipService = null)
        : base(logger)
    {
        _permissionsService = permissionsService;
        _siteListService = siteListService;
        _sessionManager = sessionManager;
        _csvExportService = csvExportService;
        _htmlExportService = htmlExportService;
        _brandingService = brandingService;
        _groupResolver = groupResolver;
        _logger = logger;
        _settingsService = settingsService;
        _ownershipService = ownershipService;

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

    /// <summary>
    /// Test constructor — omits export services (not needed for unit tests).
    /// </summary>
    internal PermissionsViewModel(
        IPermissionsService permissionsService,
        ISiteListService siteListService,
        ISessionManager sessionManager,
        ILogger<FeatureViewModelBase> logger,
        IBrandingService? brandingService = null,
        SettingsService? settingsService = null,
        IOwnershipElevationService? ownershipService = null)
        : base(logger)
    {
        _permissionsService = permissionsService;
        _siteListService = siteListService;
        _sessionManager = sessionManager;
        _csvExportService = null;
        _htmlExportService = null;
        _brandingService = brandingService;
        _logger = logger;
        _settingsService = settingsService;
        _ownershipService = ownershipService;

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

    // ── FeatureViewModelBase implementation ─────────────────────────────────

    partial void OnIsSimplifiedModeChanged(bool value)
    {
        if (value && Results.Count > 0)
            RebuildSimplifiedData();
        OnPropertyChanged(nameof(ActiveItemsSource));
    }

    partial void OnIsDetailViewChanged(bool value)
    {
        OnPropertyChanged(nameof(ActiveItemsSource));
    }

    /// <summary>
    /// Recomputes SimplifiedResults and Summaries from the current Results collection.
    /// Called when Results changes or when simplified mode is toggled on.
    /// </summary>
    private static bool IsSimplifiedModeNoise(PermissionEntry e)
    {
        if (e.Users.Contains("SharePointHome", StringComparison.OrdinalIgnoreCase)) return true;
        if (e.GrantedThrough.Contains("SharePointHome", StringComparison.OrdinalIgnoreCase)) return true;
        if (e.UserLogins.Split(';').Any(l => l.Trim().StartsWith("c:0u.c|tenant|", StringComparison.OrdinalIgnoreCase))) return true;
        return false;
    }

    private void RebuildSimplifiedData()
    {
        var forSimplified = FilteredResults.Where(e => !IsSimplifiedModeNoise(e));
        SimplifiedResults = SimplifiedPermissionEntry.WrapAll(forSimplified);
        Summaries = PermissionSummaryBuilder.Build(SimplifiedResults);
    }

    protected override async Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
    {
        var urls = GlobalSites.Select(s => s.Url).Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
        if (urls.Count == 0)
        {
            StatusMessage = TranslationSource.Instance["err.no_sites_selected"];
            return;
        }

        var nonEmpty = urls;

        var allEntries = new List<PermissionEntry>();
        var scanOptions = new ScanOptions(
            IncludeInherited: IncludeInherited,
            ScanFolders: ScanFolders,
            FolderDepth: FolderDepth,
            IncludeSubsites: IncludeSubsites);

        // Read toggle once before the loop (avoids async in exception filter)
        var autoOwnership = await IsAutoTakeOwnershipEnabled();

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

            var profile = new TenantProfile
            {
                TenantUrl = url,
                ClientId = _currentProfile?.ClientId ?? string.Empty,
                Name = _currentProfile?.Name ?? string.Empty
            };

            bool wasElevated = false;
            IReadOnlyList<PermissionEntry> siteEntries;

            try
            {
                var ctx = await _sessionManager.GetOrCreateContextAsync(profile, ct);
                siteEntries = await _permissionsService.ScanSiteAsync(ctx, scanOptions, progress, ct);
            }
            catch (Exception ex) when (IsAccessDenied(ex) && _ownershipService != null && autoOwnership)
            {
                _logger.LogWarning("Access denied on {Url}, auto-elevating ownership...", url);
                var adminUrl = DeriveAdminUrl(_currentProfile?.TenantUrl ?? url);
                var adminProfile = new TenantProfile
                {
                    TenantUrl = adminUrl,
                    ClientId = _currentProfile?.ClientId ?? string.Empty,
                    Name = _currentProfile?.Name ?? string.Empty
                };
                var adminCtx = await _sessionManager.GetOrCreateContextAsync(adminProfile, ct);
                await _ownershipService.ElevateAsync(adminCtx, url, string.Empty, ct);

                // Retry scan with fresh context
                var retryCtx = await _sessionManager.GetOrCreateContextAsync(profile, ct);
                siteEntries = await _permissionsService.ScanSiteAsync(retryCtx, scanOptions, progress, ct);
                wasElevated = true;
            }

            if (wasElevated)
                allEntries.AddRange(siteEntries.Select(e => e with { WasAutoElevated = true }));
            else
                allEntries.AddRange(siteEntries);

            i++;
        }

        // Update Results on the UI thread (no-op if no Dispatcher in tests)
        var dispatcher = Application.Current?.Dispatcher;
        if (dispatcher != null)
        {
            await dispatcher.InvokeAsync(() =>
            {
                Results = new ObservableCollection<PermissionEntry>(allEntries);
                RebuildFilteredResults();
                if (IsSimplifiedMode)
                    RebuildSimplifiedData();
                OnPropertyChanged(nameof(ActiveItemsSource));
            });
        }
        else
        {
            Results = new ObservableCollection<PermissionEntry>(allEntries);
            RebuildFilteredResults();
            if (IsSimplifiedMode)
                RebuildSimplifiedData();
            OnPropertyChanged(nameof(ActiveItemsSource));
        }

        ExportCsvCommand.NotifyCanExecuteChanged();
        ExportHtmlCommand.NotifyCanExecuteChanged();
    }

    // ── Auto-ownership helpers ───────────────────────────────────────────────

    /// <summary>
    /// Derives the tenant admin URL from a standard tenant URL.
    /// E.g. https://tenant.sharepoint.com → https://tenant-admin.sharepoint.com
    /// </summary>
    /// <summary>
    /// Extracts the site-collection root URL from an arbitrary SharePoint object URL.
    /// E.g. https://t.sharepoint.com/sites/hr/Shared%20Documents/Reports → https://t.sharepoint.com/sites/hr
    /// Falls back to scheme+host for root-collection URLs.
    /// </summary>
    internal static string DeriveSiteCollectionUrl(string objectUrl)
    {
        if (string.IsNullOrWhiteSpace(objectUrl)) return string.Empty;
        if (!Uri.TryCreate(objectUrl, UriKind.Absolute, out var uri))
            return objectUrl.TrimEnd('/');

        var baseUrl = $"{uri.Scheme}://{uri.Host}";
        var segments = uri.AbsolutePath.Trim('/').Split('/', StringSplitOptions.RemoveEmptyEntries);

        // Managed paths: /sites/<name> or /teams/<name>
        if (segments.Length >= 2 &&
            (segments[0].Equals("sites", StringComparison.OrdinalIgnoreCase) ||
             segments[0].Equals("teams", StringComparison.OrdinalIgnoreCase)))
        {
            return $"{baseUrl}/{segments[0]}/{segments[1]}";
        }

        // Root site collection
        return baseUrl;
    }

    internal static string DeriveAdminUrl(string tenantUrl)
    {
        var uri = new Uri(tenantUrl.TrimEnd('/'));
        var host = uri.Host;
        if (host.Contains("-admin.sharepoint.com", StringComparison.OrdinalIgnoreCase))
            return tenantUrl;
        var adminHost = host.Replace(".sharepoint.com", "-admin.sharepoint.com",
            StringComparison.OrdinalIgnoreCase);
        return $"{uri.Scheme}://{adminHost}";
    }

    private static bool IsAccessDenied(Exception ex)
    {
        if (ex is Microsoft.SharePoint.Client.ServerUnauthorizedAccessException) return true;
        if (ex is System.Net.WebException webEx && webEx.Response is System.Net.HttpWebResponse resp
            && resp.StatusCode == System.Net.HttpStatusCode.Forbidden) return true;
        return false;
    }

    private async Task<bool> IsAutoTakeOwnershipEnabled()
    {
        if (_settingsService == null) return false;
        var settings = await _settingsService.GetSettingsAsync();
        return settings.AutoTakeOwnership;
    }

    // ── Tenant switching ─────────────────────────────────────────────────────

    protected override void OnTenantSwitched(TenantProfile profile)
    {
        _currentProfile = profile;
        Results = new ObservableCollection<PermissionEntry>();
        FilteredResults = Array.Empty<PermissionEntry>();
        SimplifiedResults = Array.Empty<SimplifiedPermissionEntry>();
        Summaries = Array.Empty<PermissionSummary>();
        OnPropertyChanged(nameof(ActiveItemsSource));
        ExportCsvCommand.NotifyCanExecuteChanged();
        ExportHtmlCommand.NotifyCanExecuteChanged();
    }

    // ── Internal helpers ─────────────────────────────────────────────────────

    /// <summary>Sets the current tenant profile (for test injection).</summary>
    internal void SetCurrentProfile(TenantProfile profile) => _currentProfile = profile;

    /// <summary>Exposes RunOperationAsync for unit tests (internal + InternalsVisibleTo).</summary>
    internal Task TestRunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
        => RunOperationAsync(ct, progress);

    // ── Command implementations ───────────────────────────────────────────────

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

    private async Task ExportCsvAsync(CancellationToken ct)
    {
        if (_csvExportService == null || Results.Count == 0) return;
        var dialog = new SaveFileDialog
        {
            Title = "Export permissions to CSV",
            Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*",
            DefaultExt = "csv",
            FileName = "permissions"
        };
        if (dialog.ShowDialog() != true) return;
        try
        {
            if (IsSimplifiedMode && SimplifiedResults.Count > 0)
                await _csvExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CurrentSplit, ct);
            else
                await _csvExportService.WriteAsync(FilteredResults, dialog.FileName, CurrentSplit, ct);
            OpenFile(dialog.FileName);
        }
        catch (Exception ex)
        {
            StatusMessage = $"Export failed: {ex.Message}";
            _logger.LogError(ex, "CSV export failed.");
        }
    }

    private async Task ExportHtmlAsync(CancellationToken ct)
    {
        if (_htmlExportService == null || Results.Count == 0) return;
        var dialog = new SaveFileDialog
        {
            Title = "Export permissions to HTML",
            Filter = "HTML files (*.html)|*.html|All files (*.*)|*.*",
            DefaultExt = "html",
            FileName = "permissions"
        };
        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);
            }

            IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null;
            if (_groupResolver != null && Results.Count > 0 && _currentProfile != null)
            {
                // SharePoint groups live per site collection. Bucket each group
                // by the site it was observed on, then resolve against that
                // site's context. Using the root tenant ctx for a group that
                // lives on a sub-site makes CSOM fail with "Group not found".
                var groupsBySite = FilteredResults
                    .Where(r => r.PrincipalType == "SharePointGroup")
                    .SelectMany(r => r.Users
                        .Split(';', StringSplitOptions.RemoveEmptyEntries)
                        .Select(n => (SiteUrl: DeriveSiteCollectionUrl(r.Url), GroupName: n.Trim())))
                    .Where(x => x.GroupName.Length > 0)
                    .GroupBy(x => x.SiteUrl, StringComparer.OrdinalIgnoreCase)
                    .ToList();

                if (groupsBySite.Count > 0)
                {
                    var merged = new Dictionary<string, IReadOnlyList<ResolvedMember>>(
                        StringComparer.OrdinalIgnoreCase);

                    foreach (var bucket in groupsBySite)
                    {
                        var distinctNames = bucket
                            .Select(x => x.GroupName)
                            .Distinct(StringComparer.OrdinalIgnoreCase)
                            .ToList();

                        try
                        {
                            var siteProfile = new TenantProfile
                            {
                                TenantUrl = bucket.Key,
                                ClientId = _currentProfile.ClientId,
                                Name = _currentProfile.Name
                            };
                            var ctx = await _sessionManager.GetOrCreateContextAsync(
                                siteProfile, ct);
                            var resolved = await _groupResolver.ResolveGroupsAsync(
                                ctx, _currentProfile.ClientId, distinctNames, ct);
                            foreach (var kv in resolved)
                                merged[kv.Key] = kv.Value;
                        }
                        catch (Exception ex)
                        {
                            _logger.LogWarning(ex,
                                "Group resolution failed for {Site} — continuing without member expansion.",
                                bucket.Key);
                        }
                    }

                    groupMembers = merged;
                }
            }

            if (IsSimplifiedMode && SimplifiedResults.Count > 0)
                await _htmlExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CurrentSplit, CurrentLayout, ct, branding, groupMembers, HideSystemGroupRaw);
            else
                await _htmlExportService.WriteAsync(FilteredResults, dialog.FileName, CurrentSplit, CurrentLayout, ct, branding, groupMembers, HideSystemGroupRaw);
            OpenFile(dialog.FileName);
        }
        catch (Exception ex)
        {
            StatusMessage = $"Export failed: {ex.Message}";
            _logger.LogError(ex, "HTML export failed.");
        }
    }

    private static void OpenFile(string filePath)
    {
        try
        {
            Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true });
        }
        catch
        {
            // Non-critical: file was written successfully, just can't auto-open
        }
    }
}
