using System.IO;
using System.Text;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Localization;
using static SharepointToolbox.Services.Export.PermissionHtmlFragments;

namespace SharepointToolbox.Services.Export;

/// <summary>
/// Exports permission entries to a self-contained interactive HTML report.
/// Ports PowerShell <c>Export-PermissionsToHTML</c> functionality.
/// No external CSS/JS dependencies — everything is inline so the file can be
/// emailed or served from any static host. The standard and simplified
/// variants share their document shell, stats cards, CSS, pill rendering, and
/// inline script via <see cref="PermissionHtmlFragments"/>; this class only
/// owns the table column sets and the simplified risk summary.
/// </summary>
public class HtmlExportService
{
    /// <summary>
    /// Builds a self-contained HTML string from the supplied permission
    /// entries. Standard report: columns are Object / Title / URL / Unique /
    /// Users / Permission / Granted Through. When
    /// <paramref name="groupMembers"/> is provided, SharePoint group pills
    /// become expandable rows listing resolved members.
    /// </summary>
    public string BuildHtml(
        IReadOnlyList<PermissionEntry> entries,
        ReportBranding? branding = null,
        IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null,
        bool hideSystemGroupRaw = false)
    {
        var T = TranslationSource.Instance;
        var (totalEntries, uniquePermSets, distinctUsers) = ComputeStats(
            entries.Count,
            entries.Select(e => e.PermissionLevels),
            entries.SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries)));

        var sb = new StringBuilder();
        AppendHead(sb, T["report.title.permissions"], includeRiskCss: false);
        sb.AppendLine("<body>");
        sb.Append(BrandingHtmlHelper.BuildBrandingHeader(branding));
        sb.AppendLine($"<h1>{T["report.title.permissions"]}</h1>");
        AppendStatsCards(sb, totalEntries, uniquePermSets, distinctUsers);
        AppendFilterInput(sb);
        AppendTableOpen(sb);
        sb.AppendLine("<thead><tr>");
        sb.AppendLine($"  <th>{T["report.col.object"]}</th><th>{T["report.col.title"]}</th><th>{T["report.col.url"]}</th><th>{T["report.badge.unique"]}</th><th>{T["report.col.users_groups"]}</th><th>{T["report.col.permission_level"]}</th><th>{T["report.col.granted_through"]}</th>");
        sb.AppendLine("</tr></thead>");
        sb.AppendLine("<tbody>");

        int grpMemIdx = 0;
        foreach (var entry in entries)
        {
            var typeCss   = ObjectTypeCss(entry.ObjectType);
            var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited";
            var uniqueLbl = entry.HasUniquePermissions ? T["report.badge.unique"] : T["report.badge.inherited"];

            var (pills, subRows) = BuildUserPillsCell(
                entry.UserLogins, entry.Users, entry.PrincipalType, groupMembers,
                colSpan: 7, grpMemIdx: ref grpMemIdx,
                targetLabel: entry.TargetLabel, sharingLinkType: entry.SharingLinkType,
                hideSystemGroupRaw: hideSystemGroupRaw);

            sb.AppendLine("<tr>");
            sb.AppendLine($"  <td><span class=\"{typeCss}\">{HtmlEncode(entry.ObjectType)}</span></td>");
            sb.AppendLine($"  <td>{HtmlEncode(entry.Title)}</td>");
            sb.AppendLine($"  <td><a href=\"{HtmlEncode(entry.Url)}\" target=\"_blank\">{T["report.text.link"]}</a></td>");
            sb.AppendLine($"  <td><span class=\"{uniqueCss}\">{uniqueLbl}</span></td>");
            sb.AppendLine($"  <td>{pills}</td>");
            sb.AppendLine($"  <td>{HtmlEncode(entry.PermissionLevels)}</td>");
            sb.AppendLine($"  <td>{BuildGrantedThroughCell(entry.GrantedThrough, entry.TargetUrl, entry.TargetLabel, entry.SharingLinkType, hideSystemGroupRaw)}</td>");
            sb.AppendLine("</tr>");
            if (subRows.Length > 0) sb.Append(subRows);
        }

        AppendTableClose(sb);
        AppendInlineJs(sb);
        sb.AppendLine("</body>");
        sb.AppendLine("</html>");
        return sb.ToString();
    }

    /// <summary>
    /// Builds a self-contained HTML string from simplified permission entries.
    /// Adds a risk-level summary card strip plus two columns (Simplified,
    /// Risk) relative to <see cref="BuildHtml(IReadOnlyList{PermissionEntry}, ReportBranding?, IReadOnlyDictionary{string, IReadOnlyList{ResolvedMember}}?)"/>.
    /// Color-coded risk badges use <see cref="RiskLevelColors(RiskLevel)"/>.
    /// </summary>
    public string BuildHtml(
        IReadOnlyList<SimplifiedPermissionEntry> entries,
        ReportBranding? branding = null,
        IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null,
        bool hideSystemGroupRaw = false)
    {
        var T = TranslationSource.Instance;
        var summaries = PermissionSummaryBuilder.Build(entries);
        var (totalEntries, uniquePermSets, distinctUsers) = ComputeStats(
            entries.Count,
            entries.Select(e => e.PermissionLevels),
            entries.SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries)));

        var sb = new StringBuilder();
        AppendHead(sb, T["report.title.permissions_simplified"], includeRiskCss: true);
        sb.AppendLine("<body>");
        sb.Append(BrandingHtmlHelper.BuildBrandingHeader(branding));
        sb.AppendLine($"<h1>{T["report.title.permissions_simplified"]}</h1>");
        AppendStatsCards(sb, totalEntries, uniquePermSets, distinctUsers);

        sb.AppendLine("<div class=\"risk-cards\">");
        foreach (var s in summaries)
        {
            var (bg, text, border) = RiskLevelColors(s.RiskLevel);
            sb.AppendLine($"  <div class=\"risk-card\" style=\"background:{bg};color:{text};border-color:{border}\">");
            sb.AppendLine($"    <div class=\"count\">{s.Count}</div>");
            sb.AppendLine($"    <div class=\"rlabel\">{HtmlEncode(s.Label)}</div>");
            sb.AppendLine($"    <div class=\"users\">{s.DistinctUsers} {T["report.text.users_parens"]}</div>");
            sb.AppendLine("  </div>");
        }
        sb.AppendLine("</div>");

        AppendFilterInput(sb);
        AppendTableOpen(sb);
        sb.AppendLine("<thead><tr>");
        sb.AppendLine($"  <th>{T["report.col.users_groups"]}</th><th>{T["report.col.permission_level"]}</th><th>{T["report.col.simplified"]}</th><th>{T["report.col.risk"]}</th><th>{T["report.col.granted_through"]}</th>");
        sb.AppendLine("</tr></thead>");
        sb.AppendLine("<tbody>");

        int grpMemIdx = 0;
        int sectionIdx = 0;
        var groups = entries.GroupBy(e => (e.ObjectType, e.Title, e.Url)).ToList();
        foreach (var group in groups)
        {
            var sectionId = $"sec{sectionIdx++}";
            var first     = group.First();
            var typeCss   = ObjectTypeCss(group.Key.ObjectType);
            var uniqueCss = first.HasUniquePermissions ? "badge unique" : "badge inherited";
            var uniqueLbl = first.HasUniquePermissions ? T["report.badge.unique"] : T["report.badge.inherited"];
            var count     = group.Count();

            sb.AppendLine($"<tr class=\"section-header collapsed\" data-section=\"{sectionId}\">");
            sb.AppendLine($"  <td colspan=\"5\"><span class=\"chevron\">&#9660;</span><span class=\"{typeCss}\">{HtmlEncode(group.Key.ObjectType)}</span> <strong>{HtmlEncode(group.Key.Title)}</strong> <a href=\"{HtmlEncode(group.Key.Url)}\" target=\"_blank\">&#8599;</a> <span class=\"{uniqueCss}\">{uniqueLbl}</span><span class=\"entry-badge\">{count} {T["report.text.entries_unit"]}</span></td>");
            sb.AppendLine("</tr>");

            foreach (var entry in group)
            {
                var (riskBg, riskText, riskBorder) = RiskLevelColors(entry.RiskLevel);

                var (pills, subRows) = BuildUserPillsCell(
                    entry.UserLogins, entry.Inner.Users, entry.Inner.PrincipalType, groupMembers,
                    colSpan: 5, grpMemIdx: ref grpMemIdx,
                    targetLabel: entry.TargetLabel, sharingLinkType: entry.SharingLinkType,
                    hideSystemGroupRaw: hideSystemGroupRaw,
                    sectionId: sectionId);

                sb.AppendLine($"<tr data-section-member=\"{sectionId}\" style=\"display:none\">");
                sb.AppendLine($"  <td>{pills}</td>");
                sb.AppendLine($"  <td>{HtmlEncode(entry.PermissionLevels)}</td>");
                sb.AppendLine($"  <td>{HtmlEncode(entry.SimplifiedLabels)}</td>");
                sb.AppendLine($"  <td><span class=\"risk-badge\" style=\"background:{riskBg};color:{riskText};border-color:{riskBorder}\">{HtmlEncode(entry.RiskLevel.ToString())}</span></td>");
                sb.AppendLine($"  <td>{BuildGrantedThroughCell(entry.GrantedThrough, entry.TargetUrl, entry.TargetLabel, entry.SharingLinkType, hideSystemGroupRaw)}</td>");
                sb.AppendLine("</tr>");
                if (subRows.Length > 0) sb.Append(subRows);
            }
        }

        AppendTableClose(sb);
        AppendInlineJs(sb);
        sb.AppendLine("</body>");
        sb.AppendLine("</html>");
        return sb.ToString();
    }

    /// <summary>Writes the HTML report to the specified file path using UTF-8 without BOM.</summary>
    public async Task WriteAsync(
        IReadOnlyList<PermissionEntry> entries,
        string filePath,
        CancellationToken ct,
        ReportBranding? branding = null,
        IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null,
        bool hideSystemGroupRaw = false)
    {
        var html = BuildHtml(entries, branding, groupMembers, hideSystemGroupRaw);
        await ExportFileWriter.WriteHtmlAsync(filePath, html, ct);
    }

    /// <summary>Writes the simplified HTML report to the specified file path using UTF-8 without BOM.</summary>
    public async Task WriteAsync(
        IReadOnlyList<SimplifiedPermissionEntry> entries,
        string filePath,
        CancellationToken ct,
        ReportBranding? branding = null,
        IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null,
        bool hideSystemGroupRaw = false)
    {
        var html = BuildHtml(entries, branding, groupMembers, hideSystemGroupRaw);
        await ExportFileWriter.WriteHtmlAsync(filePath, html, ct);
    }

    /// <summary>
    /// Split-aware write for permission entries.
    /// Single → one file. BySite + SeparateFiles → one file per site.
    /// BySite + SingleTabbed → one file with per-site iframe tabs.
    /// </summary>
    public async Task WriteAsync(
        IReadOnlyList<PermissionEntry> entries,
        string basePath,
        ReportSplitMode splitMode,
        HtmlSplitLayout layout,
        CancellationToken ct,
        ReportBranding? branding = null,
        IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null,
        bool hideSystemGroupRaw = false)
    {
        if (splitMode != ReportSplitMode.BySite)
        {
            await WriteAsync(entries, basePath, ct, branding, groupMembers, hideSystemGroupRaw);
            return;
        }

        var partitions = CsvExportService.PartitionBySite(entries).ToList();
        if (layout == HtmlSplitLayout.SingleTabbed)
        {
            var parts = partitions
                .Select(p => (p.Label, Html: BuildHtml(p.Partition, branding, groupMembers, hideSystemGroupRaw)))
                .ToList();
            var title = TranslationSource.Instance["report.title.permissions"];
            var tabbed = ReportSplitHelper.BuildTabbedHtml(parts, title);
            await ExportFileWriter.WriteHtmlAsync(basePath, tabbed, ct);
            return;
        }

        foreach (var (label, partEntries) in partitions)
        {
            ct.ThrowIfCancellationRequested();
            var path = ReportSplitHelper.BuildPartitionPath(basePath, label);
            await WriteAsync(partEntries, path, ct, branding, groupMembers, hideSystemGroupRaw);
        }
    }

    /// <summary>Simplified-entry split variant.</summary>
    public async Task WriteAsync(
        IReadOnlyList<SimplifiedPermissionEntry> entries,
        string basePath,
        ReportSplitMode splitMode,
        HtmlSplitLayout layout,
        CancellationToken ct,
        ReportBranding? branding = null,
        IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null,
        bool hideSystemGroupRaw = false)
    {
        if (splitMode != ReportSplitMode.BySite)
        {
            await WriteAsync(entries, basePath, ct, branding, groupMembers, hideSystemGroupRaw);
            return;
        }

        var partitions = CsvExportService.PartitionBySite(entries).ToList();
        if (layout == HtmlSplitLayout.SingleTabbed)
        {
            var parts = partitions
                .Select(p => (p.Label, Html: BuildHtml(p.Partition, branding, groupMembers, hideSystemGroupRaw)))
                .ToList();
            var title = TranslationSource.Instance["report.title.permissions_simplified"];
            var tabbed = ReportSplitHelper.BuildTabbedHtml(parts, title);
            await ExportFileWriter.WriteHtmlAsync(basePath, tabbed, ct);
            return;
        }

        foreach (var (label, partEntries) in partitions)
        {
            ct.ThrowIfCancellationRequested();
            var path = ReportSplitHelper.BuildPartitionPath(basePath, label);
            await WriteAsync(partEntries, path, ct, branding, groupMembers, hideSystemGroupRaw);
        }
    }

    private static (int total, int uniquePerms, int distinctUsers) ComputeStats(
        int totalEntries,
        IEnumerable<string> permissionLevels,
        IEnumerable<string> userLogins)
    {
        var uniquePermSets = permissionLevels.Distinct().Count();
        var distinctUsers = userLogins
            .Select(u => u.Trim())
            .Where(u => u.Length > 0)
            .Distinct()
            .Count();
        return (totalEntries, uniquePermSets, distinctUsers);
    }

    private static void AppendTableOpen(StringBuilder sb)
    {
        sb.AppendLine("<div class=\"table-wrap\">");
        sb.AppendLine("<table id=\"permTable\">");
    }

    private static void AppendTableClose(StringBuilder sb)
    {
        sb.AppendLine("</tbody>");
        sb.AppendLine("</table>");
        sb.AppendLine("</div>");
    }

    /// <summary>Returns inline CSS background, text, and border colors for a risk level.</summary>
    private static (string bg, string text, string border) RiskLevelColors(RiskLevel level) => level switch
    {
        RiskLevel.High     => ("#FEE2E2", "#991B1B", "#FECACA"),
        RiskLevel.Medium   => ("#FEF3C7", "#92400E", "#FDE68A"),
        RiskLevel.Low      => ("#D1FAE5", "#065F46", "#A7F3D0"),
        RiskLevel.ReadOnly => ("#DBEAFE", "#1E40AF", "#BFDBFE"),
        _                  => ("#F3F4F6", "#374151", "#E5E7EB")
    };
}
