using System.Diagnostics;
using System.Net.Sockets;
using System.Text.Json;
using System.Text.Json.Serialization;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy;

/// <summary>
///     Cheap TCP-connect probe for the <see cref="GalaxyDriverOptions"/>-shaped driver config.
///     Parses the <c>Gateway.Endpoint</c> gRPC endpoint (e.g. <c>http://host:5001</c> or
///     <c>host:5001</c>), opens a socket and closes immediately. Surfaces a green tick +
///     latency on success; red chip + SocketError on failure; "timed out" on the caller's
///     cancellation. Does NOT exchange any gRPC frames — a richer gRPC ping probe is a
///     documented follow-up.
/// </summary>
public sealed class GalaxyDriverProbe : IDriverProbe
{
    private static readonly JsonSerializerOptions _opts = new()
    {
        PropertyNameCaseInsensitive = true,
        UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
    };

    /// <inheritdoc />
    // Matches DriverInstance.DriverType strings set by the AdminUI's GalaxyDriverPage.
    public string DriverType => GalaxyDriverFactoryExtensions.DriverTypeName;

    /// <inheritdoc />
    public async Task<DriverProbeResult> ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct)
    {
        GalaxyDriverOptions? opts;
        try { opts = JsonSerializer.Deserialize<GalaxyDriverOptions>(configJson, _opts); }
        catch (Exception ex) { return new(false, $"Config JSON is invalid: {ex.Message}", null); }
        if (opts is null) return new(false, "Config JSON deserialized to null.", null);

        var (host, port) = ExtractTarget(opts);
        if (string.IsNullOrWhiteSpace(host) || port <= 0)
            return new(false, "Config has no host/port to probe.", null);

        var sw = Stopwatch.StartNew();
        try
        {
            using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            await socket.ConnectAsync(host, port, ct);
            sw.Stop();
            return new(true, null, sw.Elapsed);
        }
        catch (SocketException ex)
        {
            return new(false, $"Connect failed: {ex.SocketErrorCode}", null);
        }
        catch (OperationCanceledException)
        {
            return new(false, $"Probe timed out after {timeout.TotalSeconds:F0}s.", null);
        }
        catch (Exception ex)
        {
            return new(false, ex.Message, null);
        }
    }

    private static (string host, int port) ExtractTarget(GalaxyDriverOptions opts)
    {
        var endpoint = opts.Gateway.Endpoint;
        if (string.IsNullOrWhiteSpace(endpoint)) return (string.Empty, 0);

        // Try absolute URI first (e.g. "http://hostname:5001" or "https://hostname:5001").
        if (Uri.TryCreate(endpoint, UriKind.Absolute, out var uri))
        {
            var host = uri.Host;
            // Uri.Port is -1 when not specified; default mxaccessgw port is 5001.
            var port = uri.Port > 0 ? uri.Port : 5001;
            return (host, port);
        }

        // Fallback: treat as "host:port" (no scheme).
        var colonIdx = endpoint.LastIndexOf(':');
        if (colonIdx > 0 && int.TryParse(endpoint[(colonIdx + 1)..], out var rawPort) && rawPort > 0)
            return (endpoint[..colonIdx], rawPort);

        // No port found — return the whole string as host with default port.
        return (endpoint, 5001);
    }
}
