using System.Text.Json;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;

namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Types.Transport;

/// <summary>
/// Commons-020: focused shape / round-trip tests for the Transport (#24) record DTOs
/// — <see cref="BundleManifest"/>, <see cref="ExportSelection"/>,
/// <see cref="ImportPreview"/>, <see cref="ImportResolution"/>, and
/// <see cref="ImportResult"/>. These records cross the Central UI ⇆ bundle file boundary
/// via System.Text.Json, so a positional/tuple slip would break bundles in the field.
/// EncryptionMetadata has its own focused tests under EncryptionMetadataTests.cs
/// (Commons-015) and is reused here only to populate manifest fixtures.
/// </summary>
public sealed class TransportRecordsTests
{
    // STM: TransportRecordsTests-Commons-020 marker — used by grep verification.

    private static readonly JsonSerializerOptions JsonOpts = new()
    {
        WriteIndented = false,
    };

    // --------------------------------------------------------------
    // BundleManifest
    // --------------------------------------------------------------

    [Fact]
    public void BundleManifest_Constructor_RoundTripsAllFields()
    {
        var summary = new BundleSummary(
            Templates: 2, TemplateFolders: 1, SharedScripts: 3,
            ExternalSystems: 1, DbConnections: 0, NotificationLists: 1,
            SmtpConfigs: 1, ApiMethods: 4);
        var contents = new List<ManifestContentEntry>
        {
            new("Template", "Pump",  1, new List<string> { "Shared.Helpers" }),
            new("Template", "Valve", 2, Array.Empty<string>()),
        };

        var manifest = new BundleManifest(
            BundleFormatVersion: 1,
            SchemaVersion: "1.0",
            CreatedAtUtc: new DateTimeOffset(2026, 5, 28, 12, 0, 0, TimeSpan.Zero),
            SourceEnvironment: "cli",
            ExportedBy: "alice",
            ScadaBridgeVersion: "0.9.0",
            ContentHash: "sha256:deadbeef",
            Encryption: null,
            Summary: summary,
            Contents: contents);

        Assert.Equal(1, manifest.BundleFormatVersion);
        Assert.Equal("1.0", manifest.SchemaVersion);
        Assert.Equal("cli", manifest.SourceEnvironment);
        Assert.Equal("alice", manifest.ExportedBy);
        Assert.Equal("0.9.0", manifest.ScadaBridgeVersion);
        Assert.Equal("sha256:deadbeef", manifest.ContentHash);
        Assert.Null(manifest.Encryption);
        Assert.Equal(summary, manifest.Summary);
        Assert.Equal(2, manifest.Contents.Count);
        Assert.Equal("Pump", manifest.Contents[0].Name);
    }

    [Fact]
    public void BundleManifest_JsonRoundTrip_PreservesAllFields()
    {
        var encryption = new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: "PBKDF2-SHA256",
            Iterations: 600_000,
            SaltB64: "c2FsdA==",
            IvB64: "aXY=");
        var summary = new BundleSummary(1, 0, 0, 0, 0, 0, 0, 0);
        var manifest = new BundleManifest(
            BundleFormatVersion: 1,
            SchemaVersion: "1.0",
            CreatedAtUtc: new DateTimeOffset(2026, 5, 28, 12, 0, 0, TimeSpan.Zero),
            SourceEnvironment: "ui",
            ExportedBy: "bob",
            ScadaBridgeVersion: "0.9.0",
            ContentHash: "sha256:abc",
            Encryption: encryption,
            Summary: summary,
            Contents: new List<ManifestContentEntry>
            {
                new("Template", "Pump", 7, new List<string> { "dep-a" }),
            });

        var json = JsonSerializer.Serialize(manifest, JsonOpts);
        var rt = JsonSerializer.Deserialize<BundleManifest>(json, JsonOpts);

        Assert.NotNull(rt);
        Assert.Equal(manifest.SourceEnvironment, rt!.SourceEnvironment);
        Assert.Equal(manifest.ContentHash, rt.ContentHash);
        Assert.Equal(manifest.Summary, rt.Summary);
        Assert.Single(rt.Contents);
        Assert.Equal("Pump", rt.Contents[0].Name);
        Assert.Equal(7, rt.Contents[0].Version);
        Assert.NotNull(rt.Encryption);
        Assert.Equal("AES-256-GCM", rt.Encryption!.Algorithm);
        Assert.Equal(600_000, rt.Encryption.Iterations);
    }

    // --------------------------------------------------------------
    // ExportSelection
    // --------------------------------------------------------------

    [Fact]
    public void ExportSelection_Constructor_PreservesAllIdLists()
    {
        var sel = new ExportSelection(
            TemplateIds: new[] { 1, 2, 3 },
            SharedScriptIds: new[] { 10 },
            ExternalSystemIds: Array.Empty<int>(),
            DatabaseConnectionIds: new[] { 20, 21 },
            NotificationListIds: Array.Empty<int>(),
            SmtpConfigurationIds: new[] { 30 },
            ApiMethodIds: new[] { 50 },
            IncludeDependencies: true);

        Assert.Equal(new[] { 1, 2, 3 }, sel.TemplateIds);
        Assert.Single(sel.SharedScriptIds);
        Assert.Empty(sel.ExternalSystemIds);
        Assert.Equal(2, sel.DatabaseConnectionIds.Count);
        Assert.True(sel.IncludeDependencies);
    }

    [Fact]
    public void ExportSelection_JsonRoundTrip_PreservesIncludeDependenciesAndIds()
    {
        var sel = new ExportSelection(
            TemplateIds: new[] { 1, 2 },
            SharedScriptIds: Array.Empty<int>(),
            ExternalSystemIds: new[] { 5 },
            DatabaseConnectionIds: Array.Empty<int>(),
            NotificationListIds: Array.Empty<int>(),
            SmtpConfigurationIds: Array.Empty<int>(),
            ApiMethodIds: Array.Empty<int>(),
            IncludeDependencies: false);

        var json = JsonSerializer.Serialize(sel, JsonOpts);
        var rt = JsonSerializer.Deserialize<ExportSelection>(json, JsonOpts);

        Assert.NotNull(rt);
        Assert.Equal(sel.TemplateIds, rt!.TemplateIds);
        Assert.Equal(sel.ExternalSystemIds, rt.ExternalSystemIds);
        Assert.False(rt.IncludeDependencies);
    }

    // --------------------------------------------------------------
    // ImportPreview
    // --------------------------------------------------------------

    [Fact]
    public void ImportPreview_Constructor_AllowsAllConflictKinds()
    {
        var sessionId = Guid.NewGuid();
        var items = new List<ImportPreviewItem>
        {
            new("Template", "Pump",  ExistingVersion: 1, IncomingVersion: 1, Kind: ConflictKind.Identical, FieldDiffJson: null, BlockerReason: null),
            new("Template", "Valve", ExistingVersion: 1, IncomingVersion: 2, Kind: ConflictKind.Modified,  FieldDiffJson: "{\"name\":\"Valve\"}", BlockerReason: null),
            new("Template", "New",   ExistingVersion: null, IncomingVersion: 1, Kind: ConflictKind.New, FieldDiffJson: null, BlockerReason: null),
            new("Template", "Bad",   ExistingVersion: 1, IncomingVersion: 5, Kind: ConflictKind.Blocker,   FieldDiffJson: null, BlockerReason: "Parameters property mismatch"),
        };

        var preview = new ImportPreview(sessionId, items);

        Assert.Equal(sessionId, preview.SessionId);
        Assert.Equal(4, preview.Items.Count);
        Assert.Equal(ConflictKind.Identical, preview.Items[0].Kind);
        Assert.Equal(ConflictKind.Modified, preview.Items[1].Kind);
        Assert.Equal(ConflictKind.New, preview.Items[2].Kind);
        Assert.Equal(ConflictKind.Blocker, preview.Items[3].Kind);
        Assert.Equal("Parameters property mismatch", preview.Items[3].BlockerReason);
    }

    [Fact]
    public void ImportPreview_JsonRoundTrip_PreservesConflictKindAndOptionalFields()
    {
        var preview = new ImportPreview(
            SessionId: Guid.NewGuid(),
            Items: new List<ImportPreviewItem>
            {
                new("Template", "X", 1, 2, ConflictKind.Modified, "{}", null),
                new("Template", "Y", null, 1, ConflictKind.New, null, null),
            });

        var json = JsonSerializer.Serialize(preview, JsonOpts);
        var rt = JsonSerializer.Deserialize<ImportPreview>(json, JsonOpts);

        Assert.NotNull(rt);
        Assert.Equal(preview.SessionId, rt!.SessionId);
        Assert.Equal(2, rt.Items.Count);
        Assert.Equal(ConflictKind.Modified, rt.Items[0].Kind);
        Assert.Equal(ConflictKind.New, rt.Items[1].Kind);
        Assert.Null(rt.Items[1].ExistingVersion);
    }

    // --------------------------------------------------------------
    // ImportResolution
    // --------------------------------------------------------------

    [Theory]
    [InlineData(ResolutionAction.Add, null)]
    [InlineData(ResolutionAction.Overwrite, null)]
    [InlineData(ResolutionAction.Skip, null)]
    [InlineData(ResolutionAction.Rename, "NewName")]
    public void ImportResolution_Constructor_PreservesAllActions(ResolutionAction action, string? renameTo)
    {
        var res = new ImportResolution("Template", "Pump", action, renameTo);
        Assert.Equal("Template", res.EntityType);
        Assert.Equal("Pump", res.Name);
        Assert.Equal(action, res.Action);
        Assert.Equal(renameTo, res.RenameTo);
    }

    [Fact]
    public void ImportResolution_JsonRoundTrip_PreservesRenameTo()
    {
        var res = new ImportResolution("Template", "Pump", ResolutionAction.Rename, "Pump_v2");

        var json = JsonSerializer.Serialize(res, JsonOpts);
        var rt = JsonSerializer.Deserialize<ImportResolution>(json, JsonOpts);

        Assert.NotNull(rt);
        Assert.Equal(ResolutionAction.Rename, rt!.Action);
        Assert.Equal("Pump_v2", rt.RenameTo);
    }

    // --------------------------------------------------------------
    // ImportResult
    // --------------------------------------------------------------

    [Fact]
    public void ImportResult_Constructor_PreservesAllCountersAndStaleIds()
    {
        var bundleImportId = Guid.NewGuid();
        var result = new ImportResult(
            BundleImportId: bundleImportId,
            Added: 3,
            Overwritten: 1,
            Skipped: 2,
            Renamed: 1,
            StaleInstanceIds: new List<int> { 100, 200, 300 },
            AuditEventCorrelation: "audit-corr-001");

        Assert.Equal(bundleImportId, result.BundleImportId);
        Assert.Equal(3, result.Added);
        Assert.Equal(1, result.Overwritten);
        Assert.Equal(2, result.Skipped);
        Assert.Equal(1, result.Renamed);
        Assert.Equal(new[] { 100, 200, 300 }, result.StaleInstanceIds);
        Assert.Equal("audit-corr-001", result.AuditEventCorrelation);
    }

    [Fact]
    public void ImportResult_JsonRoundTrip_PreservesCountsAndCorrelation()
    {
        var result = new ImportResult(
            BundleImportId: Guid.NewGuid(),
            Added: 5,
            Overwritten: 0,
            Skipped: 0,
            Renamed: 0,
            StaleInstanceIds: Array.Empty<int>(),
            AuditEventCorrelation: "audit-corr-xyz");

        var json = JsonSerializer.Serialize(result, JsonOpts);
        var rt = JsonSerializer.Deserialize<ImportResult>(json, JsonOpts);

        Assert.NotNull(rt);
        Assert.Equal(result.BundleImportId, rt!.BundleImportId);
        Assert.Equal(5, rt.Added);
        Assert.Empty(rt.StaleInstanceIds);
        Assert.Equal("audit-corr-xyz", rt.AuditEventCorrelation);
    }

    // --------------------------------------------------------------
    // Record equality sanity (catches positional/tuple slip)
    // --------------------------------------------------------------

    [Fact]
    public void TransportRecords_RecordValueEquality()
    {
        var a = new ImportResolution("Template", "Pump", ResolutionAction.Add, null);
        var b = new ImportResolution("Template", "Pump", ResolutionAction.Add, null);
        Assert.Equal(a, b);
        Assert.Equal(a.GetHashCode(), b.GetHashCode());

        var c = a with { Action = ResolutionAction.Overwrite };
        Assert.NotEqual(a, c);
    }
}
