using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;

namespace SharepointToolbox.Services.Export;

/// <summary>
/// Shared helpers for split report exports: filename partitioning, site label
/// derivation, and bundling per-partition HTML into a single tabbed document.
/// </summary>
public static class ReportSplitHelper
{
    /// <summary>
    /// Returns a file-safe variant of <paramref name="name"/>. Invalid filename
    /// characters are replaced with underscores; whitespace runs are collapsed.
    /// </summary>
    public static string SanitizeFileName(string name)
    {
        if (string.IsNullOrWhiteSpace(name)) return "part";
        var invalid = Path.GetInvalidFileNameChars();
        var sb = new StringBuilder(name.Length);
        foreach (var c in name)
            sb.Append(invalid.Contains(c) || c == ' ' ? '_' : c);
        var trimmed = sb.ToString().Trim('_');
        if (trimmed.Length > 80) trimmed = trimmed.Substring(0, 80);
        return trimmed.Length == 0 ? "part" : trimmed;
    }

    /// <summary>
    /// Given a user-selected <paramref name="basePath"/> (e.g. "C:\reports\duplicates.csv"),
    /// returns a partitioned path like "C:\reports\duplicates_{label}.csv".
    /// </summary>
    public static string BuildPartitionPath(string basePath, string partitionLabel)
    {
        var dir = Path.GetDirectoryName(basePath);
        var stem = Path.GetFileNameWithoutExtension(basePath);
        var ext = Path.GetExtension(basePath);
        var safe = SanitizeFileName(partitionLabel);
        var file = $"{stem}_{safe}{ext}";
        return string.IsNullOrEmpty(dir) ? file : Path.Combine(dir, file);
    }

    /// <summary>
    /// Extracts the site-collection root URL from an arbitrary SharePoint object URL.
    /// e.g. https://t.sharepoint.com/sites/hr/Shared%20Documents/foo.docx →
    ///      https://t.sharepoint.com/sites/hr
    /// Falls back to scheme+host for root site collections.
    /// </summary>
    public 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);

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

    /// <summary>
    /// Derives a short, human-friendly site label from a SharePoint site URL.
    /// Falls back to the raw URL (sanitized) when parsing fails.
    /// </summary>
    public static string DeriveSiteLabel(string siteUrl, string? siteTitle = null)
    {
        if (!string.IsNullOrWhiteSpace(siteTitle)) return siteTitle!;
        if (string.IsNullOrWhiteSpace(siteUrl)) return "site";
        try
        {
            var uri = new Uri(siteUrl);
            var segments = uri.AbsolutePath.Trim('/').Split('/', StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length >= 2 &&
                (segments[0].Equals("sites", StringComparison.OrdinalIgnoreCase) ||
                 segments[0].Equals("teams", StringComparison.OrdinalIgnoreCase)))
            {
                return segments[1];
            }
            return uri.Host;
        }
        catch (Exception ex) when (ex is UriFormatException or ArgumentException)
        {
            Debug.WriteLine($"[ReportSplitHelper] DeriveSiteLabel: malformed URL '{siteUrl}' ({ex.GetType().Name}: {ex.Message}) — falling back to raw value.");
            return siteUrl;
        }
    }

    /// <summary>
    /// Generic dispatcher for split-aware export: if
    /// <paramref name="splitMode"/> is not BySite, writes a single file via
    /// <paramref name="writer"/>; otherwise partitions via
    /// <paramref name="partitioner"/> and writes one file per partition,
    /// each at a filename derived from <paramref name="basePath"/> plus the
    /// partition label.
    /// </summary>
    public static async Task WritePartitionedAsync<T>(
        IReadOnlyList<T> items,
        string basePath,
        ReportSplitMode splitMode,
        Func<IReadOnlyList<T>, IEnumerable<(string Label, IReadOnlyList<T> Partition)>> partitioner,
        Func<IReadOnlyList<T>, string, CancellationToken, Task> writer,
        CancellationToken ct)
    {
        if (splitMode != ReportSplitMode.BySite)
        {
            await writer(items, basePath, ct);
            return;
        }

        foreach (var (label, partition) in partitioner(items))
        {
            ct.ThrowIfCancellationRequested();
            var path = BuildPartitionPath(basePath, label);
            await writer(partition, path, ct);
        }
    }

    /// <summary>
    /// Bundles per-partition HTML documents into one self-contained tabbed
    /// HTML. Each partition HTML is embedded in an &lt;iframe srcdoc&gt; so
    /// their inline styles and scripts remain isolated.
    /// </summary>
    public static string BuildTabbedHtml(
        IReadOnlyList<(string Label, string Html)> parts,
        string title)
    {
        var sb = new StringBuilder();
        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>{WebUtility.HtmlEncode(title)}</title>");
        sb.AppendLine("""
            <style>
              html, body { margin: 0; padding: 0; height: 100%; font-family: 'Segoe UI', Arial, sans-serif; background: #1a1a2e; }
              .tabbar { display: flex; flex-wrap: wrap; gap: 4px; background: #1a1a2e; padding: 8px; position: sticky; top: 0; z-index: 10; }
              .tab { padding: 6px 12px; background: #2d2d4e; color: #fff; cursor: pointer; border-radius: 4px;
                     font-size: 13px; user-select: none; white-space: nowrap; }
              .tab:hover { background: #3d3d6e; }
              .tab.active { background: #0078d4; }
              .frame-host { background: #f5f5f5; }
              iframe { width: 100%; height: calc(100vh - 52px); border: 0; display: none; background: #f5f5f5; }
              iframe.active { display: block; }
            </style>
            </head>
            <body>
            """);
        sb.Append("<div class=\"tabbar\">");
        for (int i = 0; i < parts.Count; i++)
        {
            var cls = i == 0 ? "tab active" : "tab";
            sb.Append($"<div class=\"{cls}\" onclick=\"showTab({i})\">{WebUtility.HtmlEncode(parts[i].Label)}</div>");
        }
        sb.AppendLine("</div>");

        for (int i = 0; i < parts.Count; i++)
        {
            var cls = i == 0 ? "active" : string.Empty;
            var escaped = EscapeForSrcdoc(parts[i].Html);
            sb.AppendLine($"<iframe class=\"{cls}\" srcdoc=\"{escaped}\"></iframe>");
        }
        sb.AppendLine("""
            <script>
              function showTab(i) {
                var frames = document.querySelectorAll('iframe');
                var tabs = document.querySelectorAll('.tab');
                for (var j = 0; j < frames.length; j++) {
                  frames[j].classList.toggle('active', i === j);
                  tabs[j].classList.toggle('active', i === j);
                }
              }
            </script>
            </body></html>
            """);
        return sb.ToString();
    }

    /// <summary>
    /// Escapes an HTML document so it can safely appear inside an
    /// &lt;iframe srcdoc="..."&gt; attribute. Only ampersands and double
    /// quotes must be encoded; angle brackets are kept literal because the
    /// parser treats srcdoc as CDATA-like content.
    /// </summary>
    private static string EscapeForSrcdoc(string html)
    {
        if (string.IsNullOrEmpty(html)) return string.Empty;
        return html
            .Replace("&", "&amp;")
            .Replace("\"", "&quot;");
    }
}
