using Akka.Actor;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ScadaLink.Commons.Messages.Artifacts;
using ScadaLink.Commons.Messages.DebugView;
using ScadaLink.Commons.Messages.Deployment;
using ScadaLink.Commons.Messages.Health;
using ScadaLink.Commons.Messages.InboundApi;
using ScadaLink.Commons.Messages.Integration;
using ScadaLink.Commons.Messages.Lifecycle;
using ScadaLink.Commons.Messages.RemoteQuery;
using ScadaLink.Communication.Actors;

namespace ScadaLink.Communication;

/// <summary>
/// Central-side service that wraps the Akka Ask pattern with per-pattern timeouts.
/// Provides a typed API for sending messages to sites and awaiting responses.
/// On connection drop, the ask times out (no central buffering per design).
/// </summary>
public class CommunicationService
{
    private readonly CommunicationOptions _options;
    private readonly ILogger<CommunicationService> _logger;
    private IActorRef? _centralCommunicationActor;

    public CommunicationService(
        IOptions<CommunicationOptions> options,
        ILogger<CommunicationService> logger)
    {
        _options = options.Value;
        _logger = logger;
    }

    /// <summary>
    /// Sets the central communication actor reference. Called during actor system startup.
    /// </summary>
    public void SetCommunicationActor(IActorRef centralCommunicationActor)
    {
        _centralCommunicationActor = centralCommunicationActor;
    }

    /// <summary>
    /// Triggers an immediate refresh of the site address cache from the database.
    /// </summary>
    public void RefreshSiteAddresses()
    {
        GetActor().Tell(new RefreshSiteAddresses());
    }

    /// <summary>
    /// Gets the central communication actor reference. Throws if not yet initialized.
    /// </summary>
    public IActorRef GetCommunicationActor()
    {
        return _centralCommunicationActor
            ?? throw new InvalidOperationException("CommunicationService not initialized. CentralCommunicationActor not set.");
    }

    private IActorRef GetActor() => GetCommunicationActor();

    // ── Pattern 1: Instance Deployment ──

    public async Task<DeploymentStatusResponse> DeployInstanceAsync(
        string siteId, DeployInstanceCommand command, CancellationToken cancellationToken = default)
    {
        _logger.LogDebug(
            "Sending DeployInstanceCommand to site {SiteId}, instance={Instance}, correlationId={DeploymentId}",
            siteId, command.InstanceUniqueName, command.DeploymentId);

        var envelope = new SiteEnvelope(siteId, command);
        return await GetActor().Ask<DeploymentStatusResponse>(
            envelope, _options.DeploymentTimeout, cancellationToken);
    }

    /// <summary>
    /// DeploymentManager-006: queries a site for the currently-applied deployment
    /// identity of a single instance. Used by the Deployment Manager before a
    /// re-deploy to reconcile against the site's actual state. Sent over the
    /// existing ClusterClient command/control transport; the Ask times out (no
    /// central buffering) if the site is unreachable, and the caller falls
    /// through to a normal deploy.
    /// </summary>
    public async Task<DeploymentStateQueryResponse> QueryDeploymentStateAsync(
        string siteId, DeploymentStateQueryRequest request, CancellationToken cancellationToken = default)
    {
        _logger.LogDebug(
            "Sending DeploymentStateQueryRequest to site {SiteId}, instance={Instance}, correlationId={CorrelationId}",
            siteId, request.InstanceUniqueName, request.CorrelationId);

        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<DeploymentStateQueryResponse>(
            envelope, _options.QueryTimeout, cancellationToken);
    }

    // ── Pattern 2: Lifecycle ──

    public async Task<InstanceLifecycleResponse> DisableInstanceAsync(
        string siteId, DisableInstanceCommand command, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, command);
        return await GetActor().Ask<InstanceLifecycleResponse>(
            envelope, _options.LifecycleTimeout, cancellationToken);
    }

    public async Task<InstanceLifecycleResponse> EnableInstanceAsync(
        string siteId, EnableInstanceCommand command, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, command);
        return await GetActor().Ask<InstanceLifecycleResponse>(
            envelope, _options.LifecycleTimeout, cancellationToken);
    }

    public async Task<InstanceLifecycleResponse> DeleteInstanceAsync(
        string siteId, DeleteInstanceCommand command, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, command);
        return await GetActor().Ask<InstanceLifecycleResponse>(
            envelope, _options.LifecycleTimeout, cancellationToken);
    }

    // ── Pattern 3: Artifact Deployment ──

    public async Task<ArtifactDeploymentResponse> DeployArtifactsAsync(
        string siteId, DeployArtifactsCommand command, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, command);
        return await GetActor().Ask<ArtifactDeploymentResponse>(
            envelope, _options.ArtifactDeploymentTimeout, cancellationToken);
    }

    // ── Pattern 4: Integration Routing ──

    public async Task<IntegrationCallResponse> RouteIntegrationCallAsync(
        string siteId, IntegrationCallRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<IntegrationCallResponse>(
            envelope, _options.IntegrationTimeout, cancellationToken);
    }

    // ── Pattern 5: Debug View ──

    public async Task<DebugViewSnapshot> SubscribeDebugViewAsync(
        string siteId, SubscribeDebugViewRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<DebugViewSnapshot>(
            envelope, _options.DebugViewTimeout, cancellationToken);
    }

    public void UnsubscribeDebugView(string siteId, UnsubscribeDebugViewRequest request)
    {
        // Tell (fire-and-forget) — no response expected
        GetActor().Tell(new SiteEnvelope(siteId, request));
    }

    // ── Pattern 6a: Debug Snapshot (one-shot, request/response) ──

    public async Task<DebugViewSnapshot> RequestDebugSnapshotAsync(
        string siteId, DebugSnapshotRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<DebugViewSnapshot>(
            envelope, _options.QueryTimeout, cancellationToken);
    }

    // ── Pattern 6b: Health Reporting (site→central, Tell) ──
    // Health reports are received by central, not sent. No method needed here.

    // ── Pattern 7: Remote Queries ──

    public async Task<EventLogQueryResponse> QueryEventLogsAsync(
        string siteId, EventLogQueryRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<EventLogQueryResponse>(
            envelope, _options.QueryTimeout, cancellationToken);
    }

    public async Task<ParkedMessageQueryResponse> QueryParkedMessagesAsync(
        string siteId, ParkedMessageQueryRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<ParkedMessageQueryResponse>(
            envelope, _options.QueryTimeout, cancellationToken);
    }

    public async Task<ParkedMessageRetryResponse> RetryParkedMessageAsync(
        string siteId, ParkedMessageRetryRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<ParkedMessageRetryResponse>(
            envelope, _options.QueryTimeout, cancellationToken);
    }

    public async Task<ParkedMessageDiscardResponse> DiscardParkedMessageAsync(
        string siteId, ParkedMessageDiscardRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<ParkedMessageDiscardResponse>(
            envelope, _options.QueryTimeout, cancellationToken);
    }

    // ── Pattern 8: Heartbeat (site→central, Tell) ──
    // Heartbeats are received by central, not sent. No method needed here.

    // ── Inbound API Cross-Site Routing (WP-4) ──

    public async Task<RouteToCallResponse> RouteToCallAsync(
        string siteId, RouteToCallRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<RouteToCallResponse>(
            envelope, _options.IntegrationTimeout, cancellationToken);
    }

    public async Task<RouteToGetAttributesResponse> RouteToGetAttributesAsync(
        string siteId, RouteToGetAttributesRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<RouteToGetAttributesResponse>(
            envelope, _options.IntegrationTimeout, cancellationToken);
    }

    public async Task<RouteToSetAttributesResponse> RouteToSetAttributesAsync(
        string siteId, RouteToSetAttributesRequest request, CancellationToken cancellationToken = default)
    {
        var envelope = new SiteEnvelope(siteId, request);
        return await GetActor().Ask<RouteToSetAttributesResponse>(
            envelope, _options.IntegrationTimeout, cancellationToken);
    }
}

/// <summary>
/// Envelope that wraps any message with a target site ID for routing.
/// Used by CentralCommunicationActor to resolve the site actor path.
/// </summary>
public record SiteEnvelope(string SiteId, object Message);
