using SharepointToolbox.Core.Models;
using SharepointToolbox.Localization;
using System.Globalization;
using System.IO;
using System.Text;

namespace SharepointToolbox.Services.Export;

/// <summary>
/// Exports a flat list of StorageNode objects to a UTF-8 BOM CSV.
/// Compatible with Microsoft Excel (BOM signals UTF-8 encoding).
/// </summary>
public class StorageCsvExportService
{
    /// <summary>
    /// Builds a single-section CSV: header row plus one row per
    /// <see cref="StorageNode"/> with library, site, file count, total size
    /// (MB), version size (MB), and last-modified date.
    /// </summary>
    public string BuildCsv(IReadOnlyList<StorageNode> nodes)
    {
        // Pre-size: ~110 chars/row + header avoids most StringBuilder growth.
        var sb = new StringBuilder(128 + nodes.Count * 110);
        WriteCsv(sb, nodes);
        return sb.ToString();
    }

    private static void WriteCsv(StringBuilder sb, IReadOnlyList<StorageNode> nodes)
    {
        var T = TranslationSource.Instance;
        // Hoist resource lookups out of the row loop: ResourceManager.GetString
        // is a culture-aware dictionary probe — caching once per export saves
        // O(rows × columns) lookups on large tenants.
        string colLibrary  = T["report.col.library"];
        string colKind     = T["stor.col.kind"];
        string colSite     = T["report.col.site"];
        string colFiles    = T["report.stat.files"];
        string colTotalMb  = T["report.col.total_size_mb"];
        string colVerMb    = T["report.col.version_size_mb"];
        string colLastMod  = T["report.col.last_modified"];

        sb.Append(colLibrary).Append(',')
          .Append(colKind).Append(',')
          .Append(colSite).Append(',')
          .Append(colFiles).Append(',')
          .Append(colTotalMb).Append(',')
          .Append(colVerMb).Append(',')
          .AppendLine(colLastMod);

        var kindLabels = BuildKindLabelCache();

        foreach (var node in nodes)
        {
            AppendCsvField(sb, node.Name).Append(',');
            AppendCsvField(sb, kindLabels[(int)node.Kind]).Append(',');
            AppendCsvField(sb, node.SiteTitle).Append(',');
            sb.Append(node.TotalFileCount).Append(',');
            AppendMb(sb, node.TotalSizeBytes).Append(',');
            AppendMb(sb, node.VersionSizeBytes).Append(',');
            if (node.LastModified.HasValue)
                AppendCsvField(sb, node.LastModified.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
            sb.AppendLine();
        }
    }

    /// <summary>Writes the library-level CSV to <paramref name="filePath"/> with UTF-8 BOM.</summary>
    public async Task WriteAsync(IReadOnlyList<StorageNode> nodes, string filePath, CancellationToken ct)
    {
        // Stream straight to disk: skip the StringBuilder→string copy and the
        // separate UTF-8 buffer that File.WriteAllTextAsync materializes.
        var sb = new StringBuilder(128 + nodes.Count * 110);
        WriteCsv(sb, nodes);
        await ExportFileWriter.WriteCsvChunksAsync(filePath, sb, ct);
    }

    /// <summary>
    /// Builds a CSV with library details followed by a file-type breakdown section.
    /// </summary>
    public string BuildCsv(IReadOnlyList<StorageNode> nodes, IReadOnlyList<FileTypeMetric> fileTypeMetrics)
    {
        var sb = new StringBuilder(192 + nodes.Count * 100 + fileTypeMetrics.Count * 40);
        WriteCsv(sb, nodes, fileTypeMetrics);
        return sb.ToString();
    }

    private static void WriteCsv(StringBuilder sb, IReadOnlyList<StorageNode> nodes, IReadOnlyList<FileTypeMetric> fileTypeMetrics)
    {
        var T = TranslationSource.Instance;
        string colLibrary  = T["report.col.library"];
        string colSite     = T["report.col.site"];
        string colFiles    = T["report.stat.files"];
        string colTotalMb  = T["report.col.total_size_mb"];
        string colVerMb    = T["report.col.version_size_mb"];
        string colLastMod  = T["report.col.last_modified"];

        sb.Append(colLibrary).Append(',')
          .Append(colSite).Append(',')
          .Append(colFiles).Append(',')
          .Append(colTotalMb).Append(',')
          .Append(colVerMb).Append(',')
          .AppendLine(colLastMod);

        foreach (var node in nodes)
        {
            AppendCsvField(sb, node.Name).Append(',');
            AppendCsvField(sb, node.SiteTitle).Append(',');
            sb.Append(node.TotalFileCount).Append(',');
            AppendMb(sb, node.TotalSizeBytes).Append(',');
            AppendMb(sb, node.VersionSizeBytes).Append(',');
            if (node.LastModified.HasValue)
                AppendCsvField(sb, node.LastModified.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
            sb.AppendLine();
        }

        if (fileTypeMetrics.Count > 0)
        {
            string colFileType = T["report.col.file_type"];
            string colSizeMb   = T["report.col.size_mb"];
            string colFileCnt  = T["report.col.file_count"];
            string noExtLabel  = T["report.text.no_extension"];

            sb.AppendLine();
            sb.Append(colFileType).Append(',')
              .Append(colSizeMb).Append(',')
              .AppendLine(colFileCnt);

            foreach (var m in fileTypeMetrics)
            {
                string label = string.IsNullOrEmpty(m.Extension) ? noExtLabel : m.Extension;
                AppendCsvField(sb, label).Append(',');
                AppendMb(sb, m.TotalSizeBytes).Append(',');
                sb.Append(m.FileCount).AppendLine();
            }
        }
    }

    /// <summary>Writes the two-section CSV (libraries + file-type breakdown) with UTF-8 BOM.</summary>
    public async Task WriteAsync(IReadOnlyList<StorageNode> nodes, IReadOnlyList<FileTypeMetric> fileTypeMetrics, string filePath, CancellationToken ct)
    {
        var sb = new StringBuilder(192 + nodes.Count * 100 + fileTypeMetrics.Count * 40);
        WriteCsv(sb, nodes, fileTypeMetrics);
        await ExportFileWriter.WriteCsvChunksAsync(filePath, sb, ct);
    }

    /// <summary>
    /// Writes storage metrics with optional per-site partitioning.
    /// Single → one file. BySite → one file per SiteTitle. File-type metrics
    /// are replicated across all partitions because the tenant-level scan
    /// does not retain per-site breakdowns.
    /// </summary>
    public Task WriteAsync(
        IReadOnlyList<StorageNode> nodes,
        IReadOnlyList<FileTypeMetric> fileTypeMetrics,
        string basePath,
        ReportSplitMode splitMode,
        CancellationToken ct)
        => ReportSplitHelper.WritePartitionedAsync(
            nodes, basePath, splitMode,
            PartitionBySite,
            (part, path, c) => WriteAsync(part, fileTypeMetrics, path, c),
            ct);

    /// <summary>
    /// Splits the flat StorageNode list into per-site slices while preserving
    /// the DFS hierarchy (each root library followed by its indented descendants).
    /// Siblings sharing a SiteTitle roll up into the same partition.
    /// </summary>
    internal static IEnumerable<(string Label, IReadOnlyList<StorageNode> Partition)> PartitionBySite(
        IReadOnlyList<StorageNode> nodes)
    {
        var buckets = new Dictionary<string, List<StorageNode>>(StringComparer.OrdinalIgnoreCase);
        string currentSite = string.Empty;
        foreach (var node in nodes)
        {
            if (node.IndentLevel == 0)
                currentSite = string.IsNullOrWhiteSpace(node.SiteTitle)
                    ? ReportSplitHelper.DeriveSiteLabel(node.Url)
                    : node.SiteTitle;
            if (!buckets.TryGetValue(currentSite, out var list))
            {
                list = new List<StorageNode>();
                buckets[currentSite] = list;
            }
            list.Add(node);
        }
        return buckets.Select(kv => (kv.Key, (IReadOnlyList<StorageNode>)kv.Value));
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private static StringBuilder AppendMb(StringBuilder sb, long bytes)
        => sb.Append((bytes / (1024.0 * 1024.0)).ToString("F2", CultureInfo.InvariantCulture));

    private static StringBuilder AppendCsvField(StringBuilder sb, string value)
        => sb.Append(CsvSanitizer.EscapeMinimal(value));

    /// <summary>
    /// Pre-resolves localized labels for every <see cref="StorageNodeKind"/>
    /// once per export, indexed by the enum's int value. Avoids a
    /// <c>ResourceManager.GetString</c> call per row in hot CSV loops.
    /// </summary>
    private static string[] BuildKindLabelCache()
    {
        var values = (StorageNodeKind[])Enum.GetValues(typeof(StorageNodeKind));
        int max = 0;
        foreach (var v in values) { int i = (int)v; if (i > max) max = i; }
        var cache = new string[max + 1];
        for (int i = 0; i < cache.Length; i++) cache[i] = ((StorageNodeKind)i).ToString();
        foreach (var v in values) cache[(int)v] = KindLabel(v);
        return cache;
    }

    private static string KindLabel(StorageNodeKind kind)
    {
        var T = TranslationSource.Instance;
        return kind switch
        {
            StorageNodeKind.Library          => T["stor.kind.library"],
            StorageNodeKind.HiddenLibrary    => T["stor.kind.hidden"],
            StorageNodeKind.PreservationHold => T["stor.kind.preservation"],
            StorageNodeKind.ListAttachments  => T["stor.kind.attachments"],
            StorageNodeKind.RecycleBin       => T["stor.kind.recyclebin"],
            StorageNodeKind.Subsite          => T["stor.kind.subsite"],
            _ => kind.ToString()
        };
    }
}
