using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Services.Export;

namespace SharepointToolbox.Tests.Services.Export;

/// <summary>
/// Unit tests for UserAccessCsvExportService (Phase 7 Plan 08).
/// Verifies: summary section, column count, RFC 4180 escaping, per-user content.
/// </summary>
public class UserAccessCsvExportServiceTests
{
    // ── Helper factory ────────────────────────────────────────────────────────

    private static UserAccessEntry MakeEntry(
        string userDisplay = "Alice Smith",
        string userLogin = "alice@contoso.com",
        string siteUrl = "https://contoso.sharepoint.com",
        string siteTitle = "Contoso",
        string objectType = "List",
        string objectTitle = "Docs",
        string objectUrl = "https://contoso.sharepoint.com/Docs",
        string permLevel = "Read",
        AccessType accessType = AccessType.Direct,
        string grantedThrough = "Direct Permissions",
        bool isHighPrivilege = false,
        bool isExternal = false) =>
        new(userDisplay, userLogin, siteUrl, siteTitle, objectType, objectTitle, objectUrl,
            permLevel, accessType, grantedThrough, isHighPrivilege, isExternal);

    private static readonly UserAccessEntry DefaultEntry = MakeEntry();

    // ── Test 1: BuildCsv includes summary section ─────────────────────────────

    [Fact]
    public void BuildCsv_includes_summary_section()
    {
        var svc = new UserAccessCsvExportService();
        var csv = svc.BuildCsv("Alice Smith", "alice@contoso.com", new[] { DefaultEntry });

        Assert.Contains("User Access Audit Report", csv);
        Assert.Contains("Alice Smith", csv);
        Assert.Contains("alice@contoso.com", csv);
        Assert.Contains("Total Accesses", csv);
        Assert.Contains("Sites", csv);
    }

    // ── Test 2: BuildCsv includes data header line ────────────────────────────

    [Fact]
    public void BuildCsv_includes_data_header()
    {
        var svc = new UserAccessCsvExportService();
        var csv = svc.BuildCsv("Alice Smith", "alice@contoso.com", new[] { DefaultEntry });

        Assert.Contains("Site", csv);
        Assert.Contains("Object Type", csv);
        Assert.Contains("Object", csv);
        Assert.Contains("Permission Level", csv);
        Assert.Contains("Access Type", csv);
        Assert.Contains("Granted Through", csv);
    }

    // ── Test 3: BuildCsv escapes double quotes (RFC 4180) ─────────────────────

    [Fact]
    public void BuildCsv_escapes_quotes()
    {
        var entryWithQuotes = MakeEntry(objectTitle: "Document \"Template\" Library");
        var svc = new UserAccessCsvExportService();
        var csv = svc.BuildCsv("Alice", "alice@contoso.com", new[] { entryWithQuotes });

        // RFC 4180: double quotes inside a quoted field are doubled
        Assert.Contains("\"\"Template\"\"", csv);
    }

    // ── Test 4: BuildCsv data rows have correct column count ──────────────────

    [Fact]
    public void BuildCsv_correct_column_count()
    {
        var svc = new UserAccessCsvExportService();
        var csv = svc.BuildCsv("Alice", "alice@contoso.com", new[] { DefaultEntry });

        // Header: Site, Object Type, Object, URL, Permission Level, Access Type,
        // Granted Through, TargetLabel, TargetUrl, SharingLinkType → 10 fields.
        var lines = csv.Split('\n', StringSplitOptions.RemoveEmptyEntries);

        var headerLine = lines.FirstOrDefault(l => l.Contains("\"Site\",\"Object Type\""));
        Assert.NotNull(headerLine);

        var fields = CountCsvFields(headerLine!);
        Assert.Equal(10, fields);
    }

    // ── Test 5: WriteSingleFileAsync includes entries for all users ───────────

    [Fact]
    public async Task WriteSingleFileAsync_includes_all_users()
    {
        var alice = MakeEntry(userDisplay: "Alice", userLogin: "alice@contoso.com");
        var bob   = MakeEntry(userDisplay: "Bob",   userLogin: "bob@contoso.com");

        var svc = new UserAccessCsvExportService();
        var tmpFile = Path.GetTempFileName();
        try
        {
            await svc.WriteSingleFileAsync(new[] { alice, bob }, tmpFile, CancellationToken.None);
            var content = await File.ReadAllTextAsync(tmpFile);

            Assert.Contains("alice@contoso.com", content);
            Assert.Contains("bob@contoso.com", content);
            Assert.Contains("Users Audited", content);
        }
        finally
        {
            File.Delete(tmpFile);
        }
    }

    // ── RPT-03-f: mergePermissions=false produces identical output to default ──

    [Fact]
    public async Task WriteSingleFileAsync_mergePermissionsfalse_produces_identical_output()
    {
        var alice1 = MakeEntry(userLogin: "alice@contoso.com", siteTitle: "Contoso", permLevel: "Read");
        var alice2 = MakeEntry(userLogin: "alice@contoso.com", siteTitle: "Dev Site", permLevel: "Read",
            siteUrl: "https://contoso.sharepoint.com/sites/dev", objectUrl: "https://contoso.sharepoint.com/sites/dev/Docs");
        var bob = MakeEntry(userDisplay: "Bob Smith", userLogin: "bob@contoso.com", permLevel: "Contribute");

        var entries = new[] { alice1, alice2, bob };
        var svc = new UserAccessCsvExportService();
        var tmpDefault = Path.GetTempFileName();
        var tmpExplicit = Path.GetTempFileName();
        try
        {
            // Default call (no mergePermissions param)
            await svc.WriteSingleFileAsync(entries, tmpDefault, CancellationToken.None);
            // Explicit mergePermissions=false
            await svc.WriteSingleFileAsync(entries, tmpExplicit, CancellationToken.None, mergePermissions: false);

            var defaultContent = await File.ReadAllBytesAsync(tmpDefault);
            var explicitContent = await File.ReadAllBytesAsync(tmpExplicit);

            Assert.Equal(defaultContent, explicitContent);
        }
        finally
        {
            File.Delete(tmpDefault);
            File.Delete(tmpExplicit);
        }
    }

    // ── RPT-03-g: mergePermissions=true writes consolidated rows ──────────────

    [Fact]
    public async Task WriteSingleFileAsync_mergePermissionstrue_writes_consolidated_rows()
    {
        // alice has 2 entries with same key (same login, permLevel, accessType, grantedThrough)
        // they should be merged into 1 row with 2 locations
        var alice1 = MakeEntry(
            userDisplay: "Alice Smith", userLogin: "alice@contoso.com",
            siteUrl: "https://contoso.sharepoint.com", siteTitle: "Contoso",
            permLevel: "Read", accessType: AccessType.Direct, grantedThrough: "Direct Permissions");
        var alice2 = MakeEntry(
            userDisplay: "Alice Smith", userLogin: "alice@contoso.com",
            siteUrl: "https://dev.sharepoint.com", siteTitle: "Dev Site",
            objectUrl: "https://dev.sharepoint.com/Docs",
            permLevel: "Read", accessType: AccessType.Direct, grantedThrough: "Direct Permissions");
        // bob has a different key — separate row
        var bob = MakeEntry(
            userDisplay: "Bob Smith", userLogin: "bob@contoso.com",
            siteTitle: "Contoso", permLevel: "Contribute",
            accessType: AccessType.Direct, grantedThrough: "Direct Permissions");

        var entries = new[] { alice1, alice2, bob };
        var svc = new UserAccessCsvExportService();
        var tmpFile = Path.GetTempFileName();
        try
        {
            await svc.WriteSingleFileAsync(entries, tmpFile, CancellationToken.None, mergePermissions: true);
            var content = await File.ReadAllTextAsync(tmpFile);

            // Header must contain consolidated columns (now includes Target* + SharingLinkType)
            Assert.Contains("\"User\",\"User Login\",\"Permission Level\",\"Access Type\",\"Granted Through\",\"TargetLabel\",\"TargetUrl\",\"SharingLinkType\",\"Locations\",\"Location Count\"", content);

            // Alice's two entries merged — locations column contains both site titles
            Assert.Contains("Contoso", content);
            Assert.Contains("Dev Site", content);

            // Bob appears as a separate row
            Assert.Contains("bob@contoso.com", content);

            // The consolidated report label should appear
            Assert.Contains("User Access Audit Report (Consolidated)", content);
        }
        finally
        {
            File.Delete(tmpFile);
        }
    }

    // ── RPT-03-g edge case: single-location consolidated entry ────────────────

    [Fact]
    public async Task WriteSingleFileAsync_mergePermissionstrue_singleLocation_noSemicolon()
    {
        var entry = MakeEntry(
            userDisplay: "Alice Smith", userLogin: "alice@contoso.com",
            siteTitle: "Contoso", permLevel: "Read",
            accessType: AccessType.Direct, grantedThrough: "Direct Permissions");

        var svc = new UserAccessCsvExportService();
        var tmpFile = Path.GetTempFileName();
        try
        {
            await svc.WriteSingleFileAsync(new[] { entry }, tmpFile, CancellationToken.None, mergePermissions: true);
            var content = await File.ReadAllTextAsync(tmpFile);

            // Should contain exactly "1" as LocationCount
            Assert.Contains("\"1\"", content);

            // Locations field for a single entry should not contain a semicolon
            // Find the data row for alice and verify no semicolon in Locations
            var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
            var dataRow = lines.FirstOrDefault(l => l.Contains("alice@contoso.com") && !l.StartsWith("\"Users"));
            Assert.NotNull(dataRow);
            // The Locations column value is "Contoso" with no semicolons
            Assert.DoesNotContain("Contoso; ", dataRow);
        }
        finally
        {
            File.Delete(tmpFile);
        }
    }

    // ── Private helpers ───────────────────────────────────────────────────────

    /// <summary>
    /// Counts the number of comma-separated fields in a CSV line by stripping
    /// surrounding quotes from each field.
    /// </summary>
    private static int CountCsvFields(string line)
    {
        // Simple RFC 4180 field counter — works for well-formed quoted fields
        int count = 1;
        bool inQuotes = false;
        for (int i = 0; i < line.Length; i++)
        {
            char c = line[i];
            if (c == '"')
            {
                if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
                    i++; // skip escaped quote
                else
                    inQuotes = !inQuotes;
            }
            else if (c == ',' && !inQuotes)
            {
                count++;
            }
        }
        return count;
    }
}
