using System.Net;
using System.Text.Json;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.InboundAPI;

namespace ZB.MOM.WW.ScadaBridge.IntegrationTests;

/// <summary>
/// WP-14: End-to-end integration tests for Phase 7 integration surfaces.
/// </summary>
public class IntegrationSurfaceTests
{
    // ── Inbound API: parameter validation + error codes ──
    // Auth re-arch (C5): the in-repo ApiKeyValidator full-flow case was removed
    // with the entity. Inbound auth now runs through the shared
    // ZB.MOM.WW.Auth.ApiKeys verifier (Bearer sbk_<keyId>_<secret>); its coverage
    // lives in the InboundAPI endpoint + Auth-library tests.

    [Fact]
    public void InboundAPI_ParameterValidation_ExtendedTypes()
    {
        // Validates the full extended type system: Boolean, Integer, Float, String, Object, List.
        var definitions = JsonSerializer.Serialize(new[]
        {
            new { Name = "flag", Type = "Boolean", Required = true },
            new { Name = "count", Type = "Integer", Required = true },
            new { Name = "ratio", Type = "Float", Required = true },
            new { Name = "name", Type = "String", Required = true },
            new { Name = "config", Type = "Object", Required = true },
            new { Name = "tags", Type = "List", Required = true }
        });

        var json = "{\"flag\":true,\"count\":42,\"ratio\":3.14,\"name\":\"test\",\"config\":{\"k\":\"v\"},\"tags\":[1,2]}";
        using var doc = JsonDocument.Parse(json);
        var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);

        Assert.True(result.IsValid);
        Assert.Equal(true, result.Parameters["flag"]);
        Assert.Equal((long)42, result.Parameters["count"]);
        Assert.Equal(3.14, result.Parameters["ratio"]);
        Assert.Equal("test", result.Parameters["name"]);
        Assert.NotNull(result.Parameters["config"]);
        Assert.NotNull(result.Parameters["tags"]);
    }

    // ── External System: error classification ──

    [Fact]
    public void ExternalSystem_ErrorClassification_TransientVsPermanent()
    {
        // WP-8: Verify the full classification spectrum
        Assert.True(ExternalSystemGateway.ErrorClassifier.IsTransient(HttpStatusCode.InternalServerError));
        Assert.True(ExternalSystemGateway.ErrorClassifier.IsTransient(HttpStatusCode.ServiceUnavailable));
        Assert.True(ExternalSystemGateway.ErrorClassifier.IsTransient(HttpStatusCode.RequestTimeout));
        Assert.True(ExternalSystemGateway.ErrorClassifier.IsTransient((HttpStatusCode)429));

        Assert.False(ExternalSystemGateway.ErrorClassifier.IsTransient(HttpStatusCode.BadRequest));
        Assert.False(ExternalSystemGateway.ErrorClassifier.IsTransient(HttpStatusCode.Unauthorized));
        Assert.False(ExternalSystemGateway.ErrorClassifier.IsTransient(HttpStatusCode.Forbidden));
        Assert.False(ExternalSystemGateway.ErrorClassifier.IsTransient(HttpStatusCode.NotFound));
    }

    // ── Notification: mock SMTP delivery ──
    // NS-019: the site-shaped NotificationDeliveryService that this case exercised
    // was removed when sites stopped delivering notifications. The central SMTP
    // delivery path is now covered end-to-end by
    // ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests.Delivery.EmailNotificationDeliveryAdapterTests;
    // no equivalent integration-surface assertion is needed here.

    // ── Script Context: integration API wiring ──

    [Fact]
    public async Task ScriptContext_ExternalSystem_Call_Wired()
    {
        // Verify that ExternalSystem.Call is accessible from ScriptRuntimeContext
        var mockClient = Substitute.For<IExternalSystemClient>();
        mockClient.CallAsync("api", "getData", null, Arg.Any<CancellationToken>())
            .Returns(new ExternalCallResult(true, "{\"value\":1}", null));

        var context = CreateMinimalScriptContext(externalSystemClient: mockClient);

        var result = await context.ExternalSystem.Call("api", "getData");

        Assert.True(result.Success);
        Assert.Equal("{\"value\":1}", result.ResponseJson);
    }

    [Fact]
    public async Task ScriptContext_Notify_Send_Wired()
    {
        // Notification Outbox: Notify.Send enqueues into the site Store-and-Forward
        // Engine and returns the NotificationId handle immediately.
        // A real temp-file LocalDb, not the shared-cache in-memory database this used
        // before: StoreAndForwardStorage takes ILocalDb now, and LocalDb has no
        // in-memory mode.
        using var localDb = ZB.MOM.WW.ScadaBridge.TestSupport.TestLocalDb.CreateTemp("NotifyWired");
        var storage = new StoreAndForward.StoreAndForwardStorage(
            localDb.Db, Microsoft.Extensions.Logging.Abstractions.NullLogger<StoreAndForward.StoreAndForwardStorage>.Instance);
        await storage.InitializeAsync();
        var saf = new StoreAndForward.StoreAndForwardService(
            storage, new StoreAndForward.StoreAndForwardOptions(),
            Microsoft.Extensions.Logging.Abstractions.NullLogger<StoreAndForward.StoreAndForwardService>.Instance);

        var context = CreateMinimalScriptContext(storeAndForward: saf);

        var notificationId = await context.Notify.To("ops").Send("Alert", "Body");

        Assert.False(string.IsNullOrEmpty(notificationId));
        var buffered = await saf.GetMessageByIdAsync(notificationId);
        Assert.NotNull(buffered);
    }

    [Fact]
    public async Task ScriptContext_ExternalSystem_NoClient_Throws()
    {
        var context = CreateMinimalScriptContext();

        await Assert.ThrowsAsync<InvalidOperationException>(
            () => context.ExternalSystem.Call("api", "method"));
    }

    [Fact]
    public async Task ScriptContext_Database_NoGateway_Throws()
    {
        var context = CreateMinimalScriptContext();

        await Assert.ThrowsAsync<InvalidOperationException>(
            () => context.Database.Connection("db"));
    }

    [Fact]
    public async Task ScriptContext_Notify_NoService_Throws()
    {
        // No Store-and-Forward Engine wired → Notify.Send cannot enqueue and throws.
        var context = CreateMinimalScriptContext();

        await Assert.ThrowsAsync<InvalidOperationException>(
            () => context.Notify.To("list").Send("subj", "body"));
    }

    private static SiteRuntime.Scripts.ScriptRuntimeContext CreateMinimalScriptContext(
        IExternalSystemClient? externalSystemClient = null,
        IDatabaseGateway? databaseGateway = null,
        StoreAndForward.StoreAndForwardService? storeAndForward = null)
    {
        // Create a minimal context — we use Substitute.For<IActorRef> which is fine since
        // we won't exercise Akka functionality in these tests.
        var actorRef = Substitute.For<Akka.Actor.IActorRef>();
        var compilationService = new SiteRuntime.Scripts.ScriptCompilationService(
            Microsoft.Extensions.Logging.Abstractions.NullLogger<SiteRuntime.Scripts.ScriptCompilationService>.Instance);
        var sharedLibrary = new SiteRuntime.Scripts.SharedScriptLibrary(
            compilationService,
            Microsoft.Extensions.Logging.Abstractions.NullLogger<SiteRuntime.Scripts.SharedScriptLibrary>.Instance);

        return new SiteRuntime.Scripts.ScriptRuntimeContext(
            actorRef,
            sharedLibrary,
            currentCallDepth: 0,
            maxCallDepth: 10,
            askTimeout: TimeSpan.FromSeconds(5),
            instanceName: "test-instance",
            logger: Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance,
            externalSystemClient: externalSystemClient,
            databaseGateway: databaseGateway,
            storeAndForward: storeAndForward,
            siteId: "test-site");
    }
}
