using System;
using System.Threading;
using System.Threading.Tasks;
using ZB.MOM.WW.MxGateway.Contracts;
using ZB.MOM.WW.MxGateway.Worker.Bootstrap;
using ZB.MOM.WW.MxGateway.Worker.Ipc;

namespace ZB.MOM.WW.MxGateway.Worker.Tests.Bootstrap;

public sealed class WorkerApplicationTests
{
    /// <summary>Verifies that valid bootstrap arguments succeed and redact nonce.</summary>
    [Fact]
    public void Run_WithValidBootstrapArguments_ReturnsSuccessAndLogsRedactedNonce()
    {
        MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret");
        MemoryWorkerLogger logger = new();

        int exitCode = ZB.MOM.WW.MxGateway.Worker.WorkerApplication.Run(
            ValidArgs(),
            environment,
            logger,
            new SucceedingPipeClient());

        Assert.Equal((int)WorkerExitCode.Success, exitCode);
        Assert.Equal(2, logger.Entries.Count);
        MemoryWorkerLogEntry entry = logger.Entries[0];
        Assert.Equal("Information", entry.Level);
        Assert.Equal("WorkerBootstrapSucceeded", entry.EventName);
        Assert.Equal("session-1", entry.Fields["session_id"]);
        Assert.Equal("mxaccess-gateway-123-session-1", entry.Fields["pipe_name"]);
        Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, entry.Fields["protocol_version"]);
        Assert.Equal("[redacted]", entry.Fields["nonce"]);
        Assert.Equal("WorkerPipeSessionCompleted", logger.Entries[1].EventName);
    }

    /// <summary>Verifies that missing arguments returns invalid arguments error.</summary>
    [Fact]
    public void Run_WithMissingRequiredArguments_ReturnsInvalidArguments()
    {
        MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret");
        MemoryWorkerLogger logger = new();

        int exitCode = ZB.MOM.WW.MxGateway.Worker.WorkerApplication.Run(
            [],
            environment,
            logger);

        Assert.Equal((int)WorkerExitCode.InvalidArguments, exitCode);
        MemoryWorkerLogEntry entry = Assert.Single(logger.Entries);
        Assert.Equal("Error", entry.Level);
        Assert.Equal("WorkerBootstrapFailed", entry.EventName);
        Assert.Equal(WorkerExitCode.InvalidArguments, entry.Fields["exit_code"]);
    }

    /// <summary>Verifies that invalid protocol version is rejected.</summary>
    [Fact]
    public void Run_WithInvalidProtocolVersion_ReturnsInvalidProtocolVersion()
    {
        MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret");
        MemoryWorkerLogger logger = new();

        int exitCode = ZB.MOM.WW.MxGateway.Worker.WorkerApplication.Run(
            ValidArgs(protocolVersion: "999"),
            environment,
            logger);

        Assert.Equal((int)WorkerExitCode.InvalidProtocolVersion, exitCode);
    }

    /// <summary>Verifies that missing nonce is detected.</summary>
    [Fact]
    public void Run_WithMissingNonce_ReturnsMissingNonce()
    {
        MemoryWorkerEnvironment environment = new();
        MemoryWorkerLogger logger = new();

        int exitCode = ZB.MOM.WW.MxGateway.Worker.WorkerApplication.Run(
            ValidArgs(),
            environment,
            logger);

        Assert.Equal((int)WorkerExitCode.MissingNonce, exitCode);
    }

    /// <summary>Verifies that pipe protocol failure returns protocol violation error.</summary>
    [Fact]
    public void Run_WithPipeProtocolFailure_ReturnsProtocolViolation()
    {
        MemoryWorkerEnvironment environment = CreateEnvironment("nonce-secret");
        MemoryWorkerLogger logger = new();

        int exitCode = ZB.MOM.WW.MxGateway.Worker.WorkerApplication.Run(
            ValidArgs(),
            environment,
            logger,
            new ThrowingPipeClient(new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.NonceMismatch,
                "Bad nonce.")));

        Assert.Equal((int)WorkerExitCode.ProtocolViolation, exitCode);
        Assert.Equal("WorkerPipeProtocolFailure", logger.Entries[1].EventName);
    }

    /// <summary>Verifies that unexpected exceptions during bootstrap are logged.</summary>
    [Fact]
    public void Run_WithUnexpectedBootstrapFailure_ReturnsUnexpectedFailure()
    {
        MemoryWorkerEnvironment environment = new(new InvalidOperationException("environment failed"));
        MemoryWorkerLogger logger = new();

        int exitCode = ZB.MOM.WW.MxGateway.Worker.WorkerApplication.Run(
            ValidArgs(),
            environment,
            logger);

        Assert.Equal((int)WorkerExitCode.UnexpectedFailure, exitCode);
        MemoryWorkerLogEntry entry = Assert.Single(logger.Entries);
        Assert.Equal("WorkerBootstrapUnexpectedFailure", entry.EventName);
        Assert.Equal(WorkerExitCode.UnexpectedFailure, entry.Fields["exit_code"]);
        Assert.Equal(typeof(InvalidOperationException).FullName, entry.Fields["exception_type"]);
    }

    private static string[] ValidArgs(string? protocolVersion = null)
    {
        return
        [
            "--session-id",
            "session-1",
            "--pipe-name",
            "mxaccess-gateway-123-session-1",
            "--protocol-version",
            protocolVersion ?? GatewayContractInfo.WorkerProtocolVersion.ToString(),
        ];
    }

    private static MemoryWorkerEnvironment CreateEnvironment(string nonce)
    {
        MemoryWorkerEnvironment environment = new();
        environment.Set(WorkerOptions.NonceEnvironmentVariableName, nonce);
        return environment;
    }

    private sealed class SucceedingPipeClient : IWorkerPipeClient
    {
        /// <inheritdoc />
        public Task RunAsync(
            WorkerOptions options,
            CancellationToken cancellationToken = default)
        {
            return Task.CompletedTask;
        }
    }

    private sealed class ThrowingPipeClient : IWorkerPipeClient
    {
        private readonly Exception _exception;

        /// <summary>Initializes the pipe client with an exception to throw.</summary>
        /// <param name="exception">Exception to throw when run.</param>
        public ThrowingPipeClient(Exception exception)
        {
            _exception = exception;
        }

        /// <inheritdoc />
        public Task RunAsync(
            WorkerOptions options,
            CancellationToken cancellationToken = default)
        {
            throw _exception;
        }
    }
}
