using SharepointToolbox.Core.Models;
using SharepointToolbox.Services.Export;

namespace SharepointToolbox.Tests.Services.Export;

/// <summary>
/// Tests for PERM-06: HTML export output.
/// </summary>
public class HtmlExportServiceTests
{
    private static PermissionEntry MakeEntry(
        string users, string userLogins,
        string url = "https://contoso.sharepoint.com/sites/A",
        string principalType = "User") =>
        new("Web", "Site A", url, true, users, userLogins, "Read", "Direct Permissions", principalType);

    private static ReportBranding MakeBranding(bool msp = true, bool client = false)
    {
        var mspLogo    = msp    ? new LogoData { Base64 = "bXNw", MimeType = "image/png" }  : null;
        var clientLogo = client ? new LogoData { Base64 = "Y2xpZW50", MimeType = "image/jpeg" } : null;
        return new ReportBranding(mspLogo, clientLogo);
    }

    private static IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>> MakeGroupMembers(
        string groupName, params ResolvedMember[] members) =>
        new Dictionary<string, IReadOnlyList<ResolvedMember>>(StringComparer.OrdinalIgnoreCase)
        {
            [groupName] = members.ToList()
        };

    [Fact]
    public void BuildHtml_WithKnownEntries_ContainsUserNames()
    {
        var entry = MakeEntry("Bob Smith", "bob@contoso.com");
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry });

        Assert.Contains("Bob Smith", html);
    }

    [Fact]
    public void BuildHtml_WithEmptyList_ReturnsValidHtml()
    {
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(Array.Empty<PermissionEntry>());

        // Must be non-empty well-formed HTML even with no data rows
        Assert.NotEmpty(html);
        Assert.Contains("<html", html, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void BuildHtml_WithExternalUser_ContainsExtHashMarker()
    {
        // External users have #EXT# in their login — HTML output should make them distinguishable
        var entry = MakeEntry(
            users: "Ext User",
            userLogins: "ext_user_domain.com#EXT#@contoso.onmicrosoft.com");

        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry });

        // The HTML should surface the external marker so admins can identify guests
        Assert.Contains("EXT", html, StringComparison.OrdinalIgnoreCase);
    }

    // ── Branding tests ────────────────────────────────────────────────────────

    [Fact]
    public void BuildHtml_WithMspBranding_ContainsMspLogoImg()
    {
        var entry = MakeEntry("Test", "test@contoso.com");
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, MakeBranding(msp: true, client: false));
        Assert.Contains("data:image/png;base64,bXNw", html);
    }

    [Fact]
    public void BuildHtml_WithNullBranding_ContainsNoLogoImg()
    {
        var entry = MakeEntry("Test", "test@contoso.com");
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry });
        Assert.DoesNotContain("data:image/png;base64,", html);
    }

    [Fact]
    public void BuildHtml_WithBothLogos_ContainsTwoImgs()
    {
        var entry = MakeEntry("Test", "test@contoso.com");
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, MakeBranding(msp: true, client: true));
        Assert.Contains("data:image/png;base64,bXNw", html);
        Assert.Contains("data:image/jpeg;base64,Y2xpZW50", html);
    }

    // ── Group expansion tests (Phase 17) ──────────────────────────────────────

    [Fact]
    public void BuildHtml_NoGroupMembers_IdenticalToDefault()
    {
        var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
        var svc = new HtmlExportService();
        var htmlDefault = svc.BuildHtml(new[] { entry });
        var htmlNullNull = svc.BuildHtml(new[] { entry }, null, null);

        Assert.Equal(htmlDefault, htmlNullNull);
    }

    [Fact]
    public void BuildHtml_WithGroupMembers_RendersExpandablePill()
    {
        var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
        var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Alice", "alice@co.com"));
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, null, groupMembers);

        Assert.Contains("data-group-target=\"grpmem0\"", html);
        Assert.Contains("class=\"user-pill group-expandable\"", html);
    }

    [Fact]
    public void BuildHtml_WithGroupMembers_RendersHiddenMemberSubRow()
    {
        var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
        var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Alice", "alice@co.com"));
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, null, groupMembers);

        Assert.Contains("data-group=\"grpmem0\"", html);
        Assert.Contains("display:none", html);
        Assert.Contains("Alice", html);
        Assert.Contains("alice@co.com", html);
    }

    [Fact]
    public void BuildHtml_WithEmptyMemberList_RendersMembersUnavailable()
    {
        var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
        var groupMembers = MakeGroupMembers("Site Members"); // empty list
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, null, groupMembers);

        Assert.Contains("members unavailable", html);
    }

    [Fact]
    public void BuildHtml_ContainsToggleGroupJs()
    {
        var entry = MakeEntry("Site Members", "i:0#.f|membership|group@contoso.com", principalType: "SharePointGroup");
        var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Alice", "alice@co.com"));
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, null, groupMembers);

        Assert.Contains("data-group-target", html);
        Assert.Contains("getAttribute('data-group-target')", html);
    }

    // ── System-group target rendering (Limited Access / SharingLinks) ────────

    [Fact]
    public void BuildHtml_WithResolvedListTarget_RendersClickableLink()
    {
        var entry = new PermissionEntry(
            ObjectType: "Site",
            Title: "Contoso HR",
            Url: "https://contoso.sharepoint.com/sites/HR",
            HasUniquePermissions: true,
            Users: "Limited Access System Group For List b20e3b22-2b09-4c99-9ba4-37b42f3a12dc",
            UserLogins: "Limited Access System Group For List b20e3b22-2b09-4c99-9ba4-37b42f3a12dc",
            PermissionLevels: "Limited Access: Edit",
            GrantedThrough: "SharePoint Group: Limited Access System Group For List b20e3b22-2b09-4c99-9ba4-37b42f3a12dc",
            PrincipalType: "SharePointGroup",
            TargetUrl: "https://contoso.sharepoint.com/sites/HR/Lists/Payroll",
            TargetLabel: "Payroll");

        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry });

        Assert.Contains("https://contoso.sharepoint.com/sites/HR/Lists/Payroll", html);
        Assert.Contains(">Payroll</a>", html);
    }

    [Fact]
    public void BuildHtml_WithSharingLinkTarget_RendersLinkTypeBadgeAndTarget()
    {
        var entry = new PermissionEntry(
            ObjectType: "Folder",
            Title: "Reports",
            Url: "https://contoso.sharepoint.com/sites/HR/Shared%20Documents/Reports",
            HasUniquePermissions: true,
            Users: "SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            UserLogins: "SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PermissionLevels: "Contribute",
            GrantedThrough: "SharePoint Group: SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PrincipalType: "SharePointGroup",
            TargetUrl: "https://contoso.sharepoint.com/sites/HR/Shared%20Documents/Reports/Q4.xlsx",
            TargetLabel: "Q4.xlsx",
            SharingLinkType: "OrganizationEdit");

        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry });

        // Friendly label, raw code preserved as tooltip
        Assert.Contains("Org link", html);
        Assert.Contains("Edit", html);
        Assert.Contains("title=\"OrganizationEdit\"", html);
        Assert.Contains("Q4.xlsx", html);
        Assert.Contains("https://contoso.sharepoint.com/sites/HR/Shared%20Documents/Reports/Q4.xlsx", html);
    }

    [Theory]
    [InlineData("OrganizationView", "Org link", "View")]
    [InlineData("AnonymousEdit",    "Anyone",   "Edit")]
    [InlineData("Direct",           "Specific people", null)]
    [InlineData("Flexible",         "Custom link",     null)]
    public void BuildHtml_FriendlyLinkLabel_ReplacesRawType(string raw, string expectedPart, string? expectedSecond)
    {
        var entry = new PermissionEntry(
            ObjectType: "File", Title: "Doc.docx",
            Url: "https://contoso.sharepoint.com/sites/HR/Docs/Doc.docx",
            HasUniquePermissions: true,
            Users: $"SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.{raw}.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            UserLogins: $"SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.{raw}.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PermissionLevels: "Contribute",
            GrantedThrough: $"SharePoint Group: SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.{raw}.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PrincipalType: "SharePointGroup",
            TargetUrl: "https://contoso.sharepoint.com/sites/HR/Docs/Doc.docx",
            TargetLabel: "Doc.docx",
            SharingLinkType: raw);

        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, null, null, hideSystemGroupRaw: true);

        Assert.Contains(expectedPart, html);
        if (expectedSecond is not null)
            Assert.Contains(expectedSecond, html);
        Assert.Contains($"title=\"{raw}\"", html);
    }

    [Fact]
    public void BuildHtml_WithSharingLinkAndHideRaw_OmitsRawGroupString()
    {
        var entry = new PermissionEntry(
            ObjectType: "File", Title: "Q4.xlsx",
            Url: "https://contoso.sharepoint.com/sites/HR/Docs/Q4.xlsx",
            HasUniquePermissions: true,
            Users: "SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            UserLogins: "SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PermissionLevels: "Contribute",
            GrantedThrough: "SharePoint Group: SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PrincipalType: "SharePointGroup",
            TargetUrl: "https://contoso.sharepoint.com/sites/HR/Docs/Q4.xlsx",
            TargetLabel: "Q4.xlsx",
            SharingLinkType: "OrganizationEdit");

        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, null, null, hideSystemGroupRaw: true);

        // Raw "SharingLinks.{guid}..." text suppressed when target resolved + flag on
        Assert.DoesNotContain("SharingLinks.e686221e", html);
        // Target link still rendered
        Assert.Contains("Q4.xlsx", html);
        Assert.Contains("OrganizationEdit", html);
    }

    [Fact]
    public void BuildHtml_WithSharingLinkButNoTargetAndHideRaw_KeepsRawAsFallback()
    {
        // Target resolution failed (deleted item) — flag on, but no TargetUrl → keep raw text.
        var entry = new PermissionEntry(
            ObjectType: "File", Title: "Q4.xlsx",
            Url: "https://contoso.sharepoint.com/sites/HR/Docs/Q4.xlsx",
            HasUniquePermissions: true,
            Users: "SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            UserLogins: "SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PermissionLevels: "Contribute",
            GrantedThrough: "SharePoint Group: SharingLinks.e686221e-d1cb-43c5-8c68-04aa7f90f329.OrganizationEdit.64c27910-66ea-421d-b0f0-8f0f72dcfaf6",
            PrincipalType: "SharePointGroup",
            SharingLinkType: "OrganizationEdit");

        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry }, null, null, hideSystemGroupRaw: true);

        Assert.Contains("SharingLinks.e686221e", html);
    }

    [Fact]
    public void BuildHtml_WithoutTarget_RendersGrantedThroughTextOnly()
    {
        // Standard SP group with no resolved target — no extra link should appear.
        var entry = MakeEntry("Owners", "ownersgroup", principalType: "SharePointGroup");
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { entry });

        Assert.DoesNotContain("&rarr;", html);
    }

    [Fact]
    public void BuildHtml_Simplified_WithGroupMembers_RendersExpandablePill()
    {
        var innerEntry = new PermissionEntry("Web", "Site A", "https://contoso.sharepoint.com/sites/A",
            true, "Site Members", "i:0#.f|membership|group@contoso.com", "Read", "Direct Permissions", "SharePointGroup");
        var simplifiedEntry = new SimplifiedPermissionEntry(innerEntry);
        var groupMembers = MakeGroupMembers("Site Members", new ResolvedMember("Bob", "bob@co.com"));
        var svc = new HtmlExportService();
        var html = svc.BuildHtml(new[] { simplifiedEntry }, null, groupMembers);

        Assert.Contains("data-group-target=\"grpmem0\"", html);
        Assert.Contains("class=\"user-pill group-expandable\"", html);
    }
}
