using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Communication;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;

/// <summary>
/// Backs the Test Run sandbox <c>Instance</c> when the run is bound to a real
/// deployed instance. Routes attribute reads/writes and sibling-script calls to
/// the instance cross-site via <see cref="CommunicationService"/> — the same
/// transport the inbound API's <c>Route.To()</c> uses. All calls run under the
/// Test Run's cancellation token, so the sandbox timeout still applies.
/// </summary>
public sealed class SandboxInstanceGateway : ISandboxInstanceGateway
{
    private readonly CommunicationService _comms;
    private readonly string _siteId;
    private readonly string _instanceUniqueName;
    private readonly CancellationToken _runToken;

    /// <summary>
    /// Initializes a new <see cref="SandboxInstanceGateway"/> bound to a specific deployed instance.
    /// </summary>
    /// <param name="comms">Communication service used to route requests to the site.</param>
    /// <param name="siteId">String identifier of the site hosting the bound instance.</param>
    /// <param name="instanceUniqueName">Unique name of the instance to route calls to.</param>
    /// <param name="runToken">Cancellation token for the test run; applied to all cross-site calls.</param>
    public SandboxInstanceGateway(
        CommunicationService comms,
        string siteId,
        string instanceUniqueName,
        CancellationToken runToken)
    {
        _comms = comms;
        _siteId = siteId;
        _instanceUniqueName = instanceUniqueName;
        _runToken = runToken;
    }

    /// <inheritdoc />
    public async Task<object?> GetAttributeAsync(string canonicalName, CancellationToken ct)
    {
        var request = new RouteToGetAttributesRequest(
            Guid.NewGuid().ToString(), _instanceUniqueName,
            new[] { canonicalName }, DateTimeOffset.UtcNow);
        var response = await _comms.RouteToGetAttributesAsync(_siteId, request, _runToken);
        if (!response.Success)
            throw new ScriptSandboxException(
                $"GetAttribute(\"{canonicalName}\") on bound instance '{_instanceUniqueName}' failed: {response.ErrorMessage}");
        return response.Values.TryGetValue(canonicalName, out var value) ? value : null;
    }

    /// <inheritdoc />
    public async Task SetAttributeAsync(string canonicalName, string value, CancellationToken ct)
    {
        var request = new RouteToSetAttributesRequest(
            Guid.NewGuid().ToString(), _instanceUniqueName,
            new Dictionary<string, string> { [canonicalName] = value }, DateTimeOffset.UtcNow);
        var response = await _comms.RouteToSetAttributesAsync(_siteId, request, _runToken);
        if (!response.Success)
            throw new ScriptSandboxException(
                $"SetAttribute(\"{canonicalName}\") on bound instance '{_instanceUniqueName}' failed: {response.ErrorMessage}");
    }

    /// <inheritdoc />
    public async Task<object?> CallScriptAsync(
        string canonicalScriptName, IReadOnlyDictionary<string, object?>? parameters, CancellationToken ct)
    {
        var request = new RouteToCallRequest(
            Guid.NewGuid().ToString(), _instanceUniqueName,
            canonicalScriptName, parameters, DateTimeOffset.UtcNow);
        var response = await _comms.RouteToCallAsync(_siteId, request, _runToken);
        if (!response.Success)
            throw new ScriptSandboxException(
                $"CallScript(\"{canonicalScriptName}\") on bound instance '{_instanceUniqueName}' failed: {response.ErrorMessage}");
        return response.ReturnValue;
    }

    /// <inheritdoc />
    public async Task<WaitResult> WaitForAttributeAsync(
        string canonicalName, string? targetValueEncoded, TimeSpan timeout, bool requireGoodQuality, CancellationToken ct)
    {
        // Reuse the inbound-API cross-site wait route (RouteToWaitForAttributeAsync) — the
        // same real event-driven waiter Route.To().WaitForAttribute() uses. ParentExecutionId
        // is null for the sandbox path (no inbound execution to link).
        var request = new RouteToWaitForAttributeRequest(
            Guid.NewGuid().ToString(), _instanceUniqueName, canonicalName,
            targetValueEncoded, timeout, DateTimeOffset.UtcNow,
            ParentExecutionId: null, RequireGoodQuality: requireGoodQuality);
        var response = await _comms.RouteToWaitForAttributeAsync(_siteId, request, _runToken);
        if (!response.Success)
            throw new ScriptSandboxException(
                $"WaitForAttribute(\"{canonicalName}\") on bound instance '{_instanceUniqueName}' failed: {response.ErrorMessage}");
        return new WaitResult(response.Matched, response.Value, response.Quality, response.TimedOut);
    }
}
