using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.Commons.Messages.InboundApi;
using ScadaLink.Commons.Types;
using ScadaLink.Communication;

namespace ScadaLink.InboundAPI;

/// <summary>
/// WP-4: Route.To() helper for cross-site calls from inbound API scripts.
/// Resolves instance to site, routes via CommunicationService, blocks until response or timeout.
/// Site unreachable returns error (no store-and-forward).
/// </summary>
public class RouteHelper
{
    private readonly IInstanceLocator _instanceLocator;
    private readonly CommunicationService _communicationService;

    public RouteHelper(
        IInstanceLocator instanceLocator,
        CommunicationService communicationService)
    {
        _instanceLocator = instanceLocator;
        _communicationService = communicationService;
    }

    /// <summary>
    /// Creates a route target for the specified instance.
    /// </summary>
    public RouteTarget To(string instanceCode)
    {
        return new RouteTarget(instanceCode, _instanceLocator, _communicationService);
    }
}

/// <summary>
/// WP-4: Represents a route target (an instance) for cross-site calls.
/// </summary>
public class RouteTarget
{
    private readonly string _instanceCode;
    private readonly IInstanceLocator _instanceLocator;
    private readonly CommunicationService _communicationService;

    internal RouteTarget(
        string instanceCode,
        IInstanceLocator instanceLocator,
        CommunicationService communicationService)
    {
        _instanceCode = instanceCode;
        _instanceLocator = instanceLocator;
        _communicationService = communicationService;
    }

    /// <summary>
    /// Calls a script on the remote instance. Synchronous from API caller's
    /// perspective. <paramref name="parameters"/> may be a dictionary or an
    /// anonymous object (<c>new { name = "Bob" }</c>) — see <see cref="ScriptArgs"/>.
    /// </summary>
    public async Task<object?> Call(
        string scriptName,
        object? parameters = null,
        CancellationToken cancellationToken = default)
    {
        var siteId = await ResolveSiteAsync(cancellationToken);
        var correlationId = Guid.NewGuid().ToString();

        var request = new RouteToCallRequest(
            correlationId, _instanceCode, scriptName, ScriptArgs.Normalize(parameters), DateTimeOffset.UtcNow);

        var response = await _communicationService.RouteToCallAsync(
            siteId, request, cancellationToken);

        if (!response.Success)
        {
            throw new InvalidOperationException(
                response.ErrorMessage ?? "Remote script call failed");
        }

        return response.ReturnValue;
    }

    /// <summary>
    /// Gets a single attribute value from the remote instance.
    /// </summary>
    public async Task<object?> GetAttribute(
        string attributeName,
        CancellationToken cancellationToken = default)
    {
        var result = await GetAttributes(new[] { attributeName }, cancellationToken);
        return result.TryGetValue(attributeName, out var value) ? value : null;
    }

    /// <summary>
    /// Gets multiple attribute values from the remote instance (batch read).
    /// </summary>
    public async Task<IReadOnlyDictionary<string, object?>> GetAttributes(
        IEnumerable<string> attributeNames,
        CancellationToken cancellationToken = default)
    {
        var siteId = await ResolveSiteAsync(cancellationToken);
        var correlationId = Guid.NewGuid().ToString();

        var request = new RouteToGetAttributesRequest(
            correlationId, _instanceCode, attributeNames.ToList(), DateTimeOffset.UtcNow);

        var response = await _communicationService.RouteToGetAttributesAsync(
            siteId, request, cancellationToken);

        if (!response.Success)
        {
            throw new InvalidOperationException(
                response.ErrorMessage ?? "Remote attribute read failed");
        }

        return response.Values;
    }

    /// <summary>
    /// Sets a single attribute value on the remote instance.
    /// </summary>
    public async Task SetAttribute(
        string attributeName,
        string value,
        CancellationToken cancellationToken = default)
    {
        await SetAttributes(
            new Dictionary<string, string> { { attributeName, value } },
            cancellationToken);
    }

    /// <summary>
    /// Sets multiple attribute values on the remote instance (batch write).
    /// </summary>
    public async Task SetAttributes(
        IReadOnlyDictionary<string, string> attributeValues,
        CancellationToken cancellationToken = default)
    {
        var siteId = await ResolveSiteAsync(cancellationToken);
        var correlationId = Guid.NewGuid().ToString();

        var request = new RouteToSetAttributesRequest(
            correlationId, _instanceCode, attributeValues, DateTimeOffset.UtcNow);

        var response = await _communicationService.RouteToSetAttributesAsync(
            siteId, request, cancellationToken);

        if (!response.Success)
        {
            throw new InvalidOperationException(
                response.ErrorMessage ?? "Remote attribute write failed");
        }
    }

    private async Task<string> ResolveSiteAsync(CancellationToken cancellationToken)
    {
        var siteId = await _instanceLocator.GetSiteIdForInstanceAsync(_instanceCode, cancellationToken);
        if (siteId == null)
        {
            throw new InvalidOperationException(
                $"Instance '{_instanceCode}' not found or has no assigned site");
        }

        return siteId;
    }
}
