using System.Net;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Security;

namespace ZB.MOM.WW.ScadaBridge.IntegrationTests;

/// <summary>
/// WP-1 (Phase 8): Full-system failover testing — Central.
/// Verifies that JWT tokens and deployment state survive central node failover.
/// Multi-process failover tests are marked with Integration trait for separate runs.
/// </summary>
public class CentralFailoverTests
{
    private static JwtTokenService CreateJwtService(string signingKey = "integration-test-signing-key-must-be-at-least-32-chars-long")
    {
        var options = Options.Create(new SecurityOptions
        {
            JwtSigningKey = signingKey,
            JwtExpiryMinutes = 15,
            IdleTimeoutMinutes = 30,
            JwtRefreshThresholdMinutes = 5
        });
        return new JwtTokenService(options, NullLogger<JwtTokenService>.Instance);
    }

    [Fact]
    public void JwtToken_GeneratedBeforeFailover_ValidatesAfterFailover()
    {
        // Simulates: generate token on node A, validate on node B (shared signing key).
        var jwtServiceA = CreateJwtService();

        var token = jwtServiceA.GenerateToken(
            displayName: "Failover User",
            username: "failover_test",
            roles: new[] { "Administrator" },
            permittedSiteIds: null);

        // Validate with a second instance (same signing key = simulated failover)
        var jwtServiceB = CreateJwtService();

        var principal = jwtServiceB.ValidateToken(token);
        Assert.NotNull(principal);

        var username = principal!.FindFirst(JwtTokenService.UsernameClaimType)?.Value;
        Assert.Equal("failover_test", username);
    }

    [Fact]
    public void JwtToken_WithSiteScopes_SurvivesRevalidation()
    {
        var jwtService = CreateJwtService();

        var token = jwtService.GenerateToken(
            displayName: "Scoped User",
            username: "scoped_user",
            roles: new[] { "Deployer" },
            permittedSiteIds: new[] { "site-1", "site-2", "site-5" });

        var principal = jwtService.ValidateToken(token);
        Assert.NotNull(principal);

        var siteIds = principal!.FindAll(JwtTokenService.SiteIdClaimType)
            .Select(c => c.Value).ToList();
        Assert.Equal(3, siteIds.Count);
        Assert.Contains("site-1", siteIds);
        Assert.Contains("site-5", siteIds);
    }

    [Fact]
    public void JwtToken_DifferentSigningKeys_FailsValidation()
    {
        // If two nodes have different signing keys, tokens from one won't validate on the other.
        var jwtServiceA = CreateJwtService("node-a-signing-key-that-is-long-enough-32chars");
        var jwtServiceB = CreateJwtService("node-b-signing-key-that-is-long-enough-32chars");

        var token = jwtServiceA.GenerateToken(
            displayName: "User",
            username: "user",
            roles: new[] { "Administrator" },
            permittedSiteIds: null);

        // Token from A should NOT validate on B (different key)
        var principal = jwtServiceB.ValidateToken(token);
        Assert.Null(principal);
    }

    [Trait("Category", "Integration")]
    [Fact]
    public void DeploymentStatus_OptimisticConcurrency_DetectsStaleWrites()
    {
        var status1 = new Commons.Messages.Deployment.DeploymentStatusResponse(
            "dep-1", "instance-1", Commons.Types.Enums.DeploymentStatus.InProgress,
            null, DateTimeOffset.UtcNow);

        var status2 = new Commons.Messages.Deployment.DeploymentStatusResponse(
            "dep-1", "instance-1", Commons.Types.Enums.DeploymentStatus.Success,
            null, DateTimeOffset.UtcNow.AddSeconds(1));

        Assert.True(status2.Timestamp > status1.Timestamp);
        Assert.Equal(Commons.Types.Enums.DeploymentStatus.Success, status2.Status);
    }

    [Fact]
    public void JwtToken_ExpiredBeforeFailover_RejectedAfterFailover()
    {
        var jwtService = CreateJwtService();

        var token = jwtService.GenerateToken(
            displayName: "Expired User",
            username: "expired_user",
            roles: new[] { "Administrator" },
            permittedSiteIds: null);

        var principal = jwtService.ValidateToken(token);
        Assert.NotNull(principal);

        var expClaim = principal!.FindFirst("exp");
        Assert.NotNull(expClaim);
    }

    [Fact]
    public void JwtToken_IdleTimeout_Detected()
    {
        var jwtService = CreateJwtService();

        var token = jwtService.GenerateToken(
            displayName: "Idle User",
            username: "idle_user",
            roles: new[] { "Viewer" },
            permittedSiteIds: null);

        var principal = jwtService.ValidateToken(token);
        Assert.NotNull(principal);

        // Token was just generated — should NOT be idle timed out
        Assert.False(jwtService.IsIdleTimedOut(principal!));
    }

    [Fact]
    public void JwtToken_ShouldRefresh_DetectsNearExpiry()
    {
        var jwtService = CreateJwtService();

        var token = jwtService.GenerateToken(
            displayName: "User",
            username: "user",
            roles: new[] { "Administrator" },
            permittedSiteIds: null);

        var principal = jwtService.ValidateToken(token);
        Assert.NotNull(principal);

        // Token was just generated with 15min expiry and 5min threshold — NOT near expiry
        Assert.False(jwtService.ShouldRefresh(principal!));
    }

    [Trait("Category", "Integration")]
    [Fact]
    public void DeploymentStatus_MultipleInstances_IndependentTracking()
    {
        var statuses = new[]
        {
            new Commons.Messages.Deployment.DeploymentStatusResponse(
                "dep-1", "instance-1", Commons.Types.Enums.DeploymentStatus.Success,
                null, DateTimeOffset.UtcNow),
            new Commons.Messages.Deployment.DeploymentStatusResponse(
                "dep-1", "instance-2", Commons.Types.Enums.DeploymentStatus.InProgress,
                null, DateTimeOffset.UtcNow),
            new Commons.Messages.Deployment.DeploymentStatusResponse(
                "dep-1", "instance-3", Commons.Types.Enums.DeploymentStatus.Failed,
                "Compilation error", DateTimeOffset.UtcNow),
        };

        Assert.Equal(3, statuses.Length);
        Assert.All(statuses, s => Assert.Equal("dep-1", s.DeploymentId));
        Assert.Equal(3, statuses.Select(s => s.InstanceUniqueName).Distinct().Count());
    }
}
