using System.Text;
using SharepointToolbox.Core.Helpers;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Localization;

namespace SharepointToolbox.Services.Export;

/// <summary>
/// Shared HTML-rendering fragments for the permission exports (standard and
/// simplified). Extracted so the two <see cref="HtmlExportService"/> variants
/// share the document shell, stats cards, filter input, user-pill logic, and
/// inline script — leaving each caller only its own table headers and row
/// cells to render.
/// </summary>
internal static class PermissionHtmlFragments
{
    internal const string BaseCss = @"
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
h1 { padding: 20px 24px 10px; font-size: 1.5rem; color: #1a1a2e; }
.stats { display: flex; gap: 16px; padding: 0 24px 16px; flex-wrap: wrap; }
.stat-card { background: #fff; border-radius: 8px; padding: 14px 20px; min-width: 160px; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
.stat-card .value { font-size: 2rem; font-weight: 700; color: #1a1a2e; }
.stat-card .label { font-size: .8rem; color: #666; margin-top: 2px; }
.filter-wrap { padding: 0 24px 12px; }
#filter { width: 320px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: .95rem; }
.table-wrap { overflow-x: auto; padding: 0 24px 32px; }
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
th { background: #1a1a2e; color: #fff; padding: 10px 14px; text-align: left; font-size: .85rem; white-space: nowrap; }
td { padding: 9px 14px; border-bottom: 1px solid #eee; vertical-align: top; font-size: .875rem; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: #fafafa; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; white-space: nowrap; }
.badge.site-coll { background: #dbeafe; color: #1e40af; }
.badge.site      { background: #dcfce7; color: #166534; }
.badge.list      { background: #fef9c3; color: #854d0e; }
.badge.folder    { background: #f3f4f6; color: #374151; }
.badge.unique    { background: #dcfce7; color: #166534; }
.badge.inherited { background: #f3f4f6; color: #374151; }
.user-pill { display: inline-block; background: #e0e7ff; color: #3730a3; border-radius: 12px; padding: 2px 10px; font-size: .75rem; margin: 2px 3px 2px 0; white-space: nowrap; }
.user-pill.external-user { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
.group-expandable { cursor: pointer; }
.group-expandable:hover { opacity: 0.8; }
a { color: #2563eb; text-decoration: none; }
a:hover { text-decoration: underline; }
";

    internal const string RiskCardsCss = @"
.risk-cards { display: flex; gap: 12px; padding: 0 24px 16px; flex-wrap: wrap; }
.risk-card { border-radius: 8px; padding: 12px 18px; min-width: 140px; border: 1px solid; }
.risk-card .count { font-size: 1.5rem; font-weight: 700; }
.risk-card .rlabel { font-size: .8rem; margin-top: 2px; }
.risk-card .users { font-size: .7rem; margin-top: 2px; opacity: 0.8; }
.risk-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; border: 1px solid; }
.section-header td { background: #edf2f7; font-weight: 600; cursor: pointer; padding: 8px 14px; border-bottom: 2px solid #cbd5e0; user-select: none; }
.section-header:hover td { background: #e2e8f0; }
.section-header .chevron { margin-right: 8px; display: inline-block; transition: transform 0.15s; }
.section-header.collapsed .chevron { transform: rotate(-90deg); }
.entry-badge { display: inline-block; background: #e2e8f0; color: #4a5568; border-radius: 10px; padding: 1px 8px; font-size: .75rem; font-weight: 600; margin-left: 8px; }
";

    internal const string InlineJs = @"function filterTable() {
  var input = document.getElementById('filter').value.toLowerCase();
  var sections = document.querySelectorAll('#permTable tbody tr.section-header');
  if (sections.length === 0) {
    document.querySelectorAll('#permTable tbody tr').forEach(function(row) {
      if (row.hasAttribute('data-group')) return;
      row.style.display = row.textContent.toLowerCase().indexOf(input) > -1 ? '' : 'none';
    });
    return;
  }
  if (!input) {
    sections.forEach(function(hdr) {
      hdr.style.display = '';
      var sid = hdr.getAttribute('data-section');
      var collapsed = hdr.classList.contains('collapsed');
      document.querySelectorAll('[data-section-member=' + sid + ']:not([data-group])').forEach(function(r) {
        r.style.display = collapsed ? 'none' : '';
      });
    });
    return;
  }
  sections.forEach(function(hdr) {
    var sid = hdr.getAttribute('data-section');
    var members = document.querySelectorAll('[data-section-member=' + sid + ']:not([data-group])');
    var anyMatch = false;
    members.forEach(function(r) {
      var match = r.textContent.toLowerCase().indexOf(input) > -1;
      r.style.display = match ? '' : 'none';
      if (match) anyMatch = true;
    });
    if (!anyMatch && hdr.textContent.toLowerCase().indexOf(input) > -1) {
      anyMatch = true;
      members.forEach(function(r) { r.style.display = ''; });
    }
    hdr.style.display = anyMatch ? '' : 'none';
  });
}
document.addEventListener('click', function(ev) {
  var hdr = ev.target.closest('.section-header');
  if (hdr) {
    var sid = hdr.getAttribute('data-section');
    hdr.classList.toggle('collapsed');
    var collapsed = hdr.classList.contains('collapsed');
    document.querySelectorAll('[data-section-member=' + sid + ']').forEach(function(r) {
      if (r.hasAttribute('data-group')) { r.style.display = 'none'; return; }
      r.style.display = collapsed ? 'none' : '';
    });
    return;
  }
  var trigger = ev.target.closest('.group-expandable');
  if (!trigger) return;
  var id = trigger.getAttribute('data-group-target');
  if (!id) return;
  document.querySelectorAll('#permTable tbody tr').forEach(function(r) {
    if (r.getAttribute('data-group') === id) {
      r.style.display = r.style.display === 'none' ? '' : 'none';
    }
  });
});";

    /// <summary>
    /// Appends the shared HTML head (doctype, meta, inline CSS, title) to
    /// <paramref name="sb"/>. Pass <paramref name="includeRiskCss"/> when the
    /// caller renders risk cards/badges (simplified report only).
    /// </summary>
    internal static void AppendHead(StringBuilder sb, string title, bool includeRiskCss)
    {
        sb.AppendLine("<!DOCTYPE html>");
        sb.AppendLine("<html lang=\"en\">");
        sb.AppendLine("<head>");
        sb.AppendLine("<meta charset=\"UTF-8\">");
        sb.AppendLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
        sb.AppendLine($"<title>{title}</title>");
        sb.AppendLine("<style>");
        sb.AppendLine(BaseCss);
        if (includeRiskCss)
            sb.AppendLine(RiskCardsCss);
        sb.AppendLine("</style>");
        sb.AppendLine("</head>");
    }

    /// <summary>
    /// Appends the three stat cards (total entries, unique permission sets,
    /// distinct users/groups) inside a single <c>.stats</c> row.
    /// </summary>
    internal static void AppendStatsCards(StringBuilder sb, int totalEntries, int uniquePermSets, int distinctUsers)
    {
        var T = TranslationSource.Instance;
        sb.AppendLine("<div class=\"stats\">");
        sb.AppendLine($"  <div class=\"stat-card\"><div class=\"value\">{totalEntries}</div><div class=\"label\">{T["report.stat.total_entries"]}</div></div>");
        sb.AppendLine($"  <div class=\"stat-card\"><div class=\"value\">{uniquePermSets}</div><div class=\"label\">{T["report.stat.unique_permission_sets"]}</div></div>");
        sb.AppendLine($"  <div class=\"stat-card\"><div class=\"value\">{distinctUsers}</div><div class=\"label\">{T["report.stat.distinct_users_groups"]}</div></div>");
        sb.AppendLine("</div>");
    }

    /// <summary>Appends the live-filter input bound to <c>#permTable</c>.</summary>
    internal static void AppendFilterInput(StringBuilder sb)
    {
        var T = TranslationSource.Instance;
        sb.AppendLine("<div class=\"filter-wrap\">");
        sb.AppendLine($"  <input type=\"text\" id=\"filter\" placeholder=\"{T["report.filter.placeholder_permissions"]}\" onkeyup=\"filterTable()\" />");
        sb.AppendLine("</div>");
    }

    /// <summary>Appends the inline &lt;script&gt; that powers filter and group toggle.</summary>
    internal static void AppendInlineJs(StringBuilder sb)
    {
        sb.AppendLine("<script>");
        sb.AppendLine(InlineJs);
        sb.AppendLine("</script>");
    }

    /// <summary>
    /// Renders the user-pill cell content plus any group-member sub-rows for a
    /// single permission entry. Callers pass their row colspan so sub-rows
    /// span the full table; <paramref name="grpMemIdx"/> must be mutated
    /// across rows so sub-row IDs stay unique.
    /// </summary>
    internal static (string Pills, string MemberSubRows) BuildUserPillsCell(
        string userLogins,
        string userNames,
        string? principalType,
        IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers,
        int colSpan,
        ref int grpMemIdx,
        string? targetLabel = null,
        string? sharingLinkType = null,
        bool hideSystemGroupRaw = false,
        string? sectionId = null)
    {
        var T = TranslationSource.Instance;
        var logins = userLogins.Split(';', StringSplitOptions.RemoveEmptyEntries);
        var names  = userNames.Split(';', StringSplitOptions.RemoveEmptyEntries);
        var pills = new StringBuilder();
        var subRows = new StringBuilder();

        for (int i = 0; i < logins.Length; i++)
        {
            var login = logins[i].Trim();
            var name  = i < names.Length ? names[i].Trim() : login;
            var isExt = login.Contains("#EXT#", StringComparison.OrdinalIgnoreCase);

            // When the principal is a resolved system group and the user wants the raw
            // name hidden, replace the pill's visible text with the link-type badge
            // (sharing links) and/or the target label. Falls back to the raw name when
            // resolution failed (no targetLabel).
            var classification = principalType == "SharePointGroup"
                ? PermissionEntryHelper.Classify(name)
                : new SystemGroupClassification(SystemGroupKind.None, null, null, null, null, null);
            bool isResolvedSystemGroup = hideSystemGroupRaw
                && classification.Kind != SystemGroupKind.None
                && classification.Kind != SystemGroupKind.LimitedAccessBare
                && !string.IsNullOrEmpty(targetLabel);

            bool hasResolvedMembers = principalType == "SharePointGroup"
                && groupMembers != null
                && groupMembers.TryGetValue(name, out _);

            if (hasResolvedMembers && groupMembers!.TryGetValue(name, out var resolved))
            {
                if (resolved.Count == 0)
                {
                    // Members unavailable — render plain pill, skip expandable sub-row.
                    var cls2 = isResolvedSystemGroup ? "user-pill\" data-system-group=\"1" : "user-pill";
                    pills.Append($"<span class=\"{cls2}\" title=\"{HtmlEncode(T["report.text.empty_group"])}\" data-email=\"{HtmlEncode(login)}\">");
                    if (isResolvedSystemGroup)
                    {
                        if (!string.IsNullOrEmpty(sharingLinkType))
                            pills.Append(BuildSharingLinkBadge(sharingLinkType!));
                        pills.Append(HtmlEncode(targetLabel!));
                    }
                    else
                    {
                        pills.Append(HtmlEncode(name));
                    }
                    pills.Append("</span>");
                }
                else
                {
                    var grpId = $"grpmem{grpMemIdx}";
                    pills.Append("<span class=\"user-pill group-expandable\"");
                    if (isResolvedSystemGroup)
                        pills.Append(" data-system-group=\"1\"");
                    pills.Append($" data-group-target=\"{HtmlEncode(grpId)}\" data-email=\"{HtmlEncode(login)}\">");
                    if (isResolvedSystemGroup)
                    {
                        if (!string.IsNullOrEmpty(sharingLinkType))
                            pills.Append(BuildSharingLinkBadge(sharingLinkType!));
                        pills.Append(HtmlEncode(targetLabel!));
                    }
                    else
                    {
                        pills.Append(HtmlEncode(name));
                    }
                    pills.Append(" &#9660;</span>");

                    var parts = resolved.Select(m => $"{HtmlEncode(m.DisplayName)} &lt;{HtmlEncode(m.Login)}&gt;");
                    var memberContent = string.Join(" &bull; ", parts);
                    var sectionAttr = sectionId != null ? $" data-section-member=\"{HtmlEncode(sectionId)}\"" : "";
                    subRows.AppendLine($"<tr data-group=\"{HtmlEncode(grpId)}\"{sectionAttr} style=\"display:none\"><td colspan=\"{colSpan}\" style=\"padding-left:2em;font-size:.8rem;color:#555\">{memberContent}</td></tr>");
                    grpMemIdx++;
                }
            }
            else if (isResolvedSystemGroup)
            {
                pills.Append("<span class=\"user-pill\" data-system-group=\"1\">");
                if (!string.IsNullOrEmpty(sharingLinkType))
                    pills.Append(BuildSharingLinkBadge(sharingLinkType!));
                pills.Append(HtmlEncode(targetLabel!));
                pills.Append("</span>");
            }
            else
            {
                var cls = isExt ? "user-pill external-user" : "user-pill";
                pills.Append($"<span class=\"{cls}\" data-email=\"{HtmlEncode(login)}\">{HtmlEncode(name)}</span>");
            }
        }

        return (pills.ToString(), subRows.ToString());
    }

    /// <summary>
    /// Renders the Granted Through cell. When the entry carries a resolved system-group
    /// target (Limited Access For Web/List or SharingLinks), a clickable link to the
    /// targeted resource is appended on a second line. For sharing links the link type
    /// (OrganizationEdit / AnonymousView / …) is surfaced alongside the target.
    ///
    /// When <paramref name="hideSystemGroupRaw"/> is true and a target was resolved, the
    /// raw "SharePoint Group: SharingLinks.{guid}…" / "Limited Access System Group For
    /// Web|List {guid}" prefix is suppressed and only the link-type badge + clickable
    /// target are shown — keeps the report readable without losing information.
    /// </summary>
    internal static string BuildGrantedThroughCell(
        string grantedThrough,
        string? targetUrl,
        string? targetLabel,
        string? sharingLinkType,
        bool hideSystemGroupRaw = false)
    {
        var hasTarget = !string.IsNullOrEmpty(targetUrl) && !string.IsNullOrEmpty(targetLabel);
        var hasLinkType = !string.IsNullOrEmpty(sharingLinkType);
        var suppressRaw = hideSystemGroupRaw && hasTarget;

        var sb = new StringBuilder();
        if (!suppressRaw)
            sb.Append(HtmlEncode(grantedThrough));

        if (!hasTarget && !hasLinkType)
            return sb.ToString();

        if (suppressRaw)
        {
            // Inline layout — no leading raw text to wrap under.
            if (hasLinkType)
                sb.Append(BuildSharingLinkBadge(sharingLinkType!));
            if (hasTarget)
            {
                sb.Append("<a href=\"");
                sb.Append(HtmlEncode(targetUrl!));
                sb.Append("\" target=\"_blank\">");
                sb.Append(HtmlEncode(targetLabel!));
                sb.Append("</a>");
            }
            return sb.ToString();
        }

        sb.Append("<div style=\"margin-top:4px;font-size:.75rem;color:#555\">");
        if (hasLinkType)
            sb.Append(BuildSharingLinkBadge(sharingLinkType!));
        if (hasTarget)
        {
            sb.Append("&rarr; <a href=\"");
            sb.Append(HtmlEncode(targetUrl!));
            sb.Append("\" target=\"_blank\">");
            sb.Append(HtmlEncode(targetLabel!));
            sb.Append("</a>");
        }
        sb.Append("</div>");
        return sb.ToString();
    }

    /// <summary>
    /// Builds the colored badge for a SharePoint sharing-link type. Translates the
    /// raw <c>linkType</c> code (e.g. <c>OrganizationEdit</c>) into a human label
    /// (e.g. <c>Org link · Edit</c>) and tints by risk tier; raw code surfaces as a
    /// <c>title</c> tooltip so operators can still trace it back to the source.
    /// </summary>
    internal static string BuildSharingLinkBadge(string rawLinkType)
    {
        var (label, risk) = SharingLinkLabels.Describe(rawLinkType);
        var (bg, fg) = SharingLinkLabels.Colors(risk);
        return $"<span class=\"badge\" style=\"background:{bg};color:{fg};margin-right:6px\" " +
               $"title=\"{HtmlEncode(rawLinkType)}\">{HtmlEncode(label)}</span>";
    }

    /// <summary>Returns the CSS class for the object-type badge.</summary>
    internal static string ObjectTypeCss(string t) => t switch
    {
        "Site Collection" => "badge site-coll",
        "Site"            => "badge site",
        "List"            => "badge list",
        "Folder"          => "badge folder",
        _                 => "badge"
    };

    /// <summary>Minimal HTML encoding for text content and attribute values.</summary>
    internal static string HtmlEncode(string value)
    {
        if (string.IsNullOrEmpty(value)) return string.Empty;
        return value
            .Replace("&",  "&amp;")
            .Replace("<",  "&lt;")
            .Replace(">",  "&gt;")
            .Replace("\"", "&quot;")
            .Replace("'",  "&#39;");
    }
}
