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

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

/// <summary>
///     Cheap TCP-connect probe for the <see cref="TwinCATDriverOptions"/>-shaped driver config.
///     Opens a socket to the first device's AMS router host (first four octets of the AMS Net ID)
///     on the AMS port from the address 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 ADS bytes — a richer ADS-state probe is a documented follow-up.
/// </summary>
/// <remarks>
///     AMS Net ID format is six dot-separated octets (e.g. <c>192.168.1.10.1.1</c>); the first
///     four are typically the host IPv4 address by Beckhoff convention, but the AMS router
///     resolves the real IP route server-side. The probe uses the first-four-octet heuristic
///     which is reliable for the overwhelming majority of production deployments.
/// </remarks>
public sealed class TwinCATDriverProbe : IDriverProbe
{
    private static readonly JsonSerializerOptions _opts = new()
    {
        PropertyNameCaseInsensitive = true,
        UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
    };

    /// <inheritdoc />
    public string DriverType => "TwinCAT";

    /// <inheritdoc />
    public async Task<DriverProbeResult> ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct)
    {
        TwinCATDriverOptions? opts;
        try { opts = JsonSerializer.Deserialize<TwinCATDriverOptions>(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(TwinCATDriverOptions opts)
    {
        // Parse the first device's ads:// address.  AMS Net ID is six-octet; by Beckhoff
        // convention the first four octets are the host IPv4.  Extract those as the TCP target.
        var firstDevice = opts.Devices.FirstOrDefault();
        if (firstDevice is null) return (string.Empty, 0);

        var parsed = TwinCATAmsAddress.TryParse(firstDevice.HostAddress);
        if (parsed is null) return (string.Empty, 0);

        // NetId = "a.b.c.d.e.f" — take the first 4 octets as the host IP.
        var parts = parsed.NetId.Split('.');
        if (parts.Length < 4) return (string.Empty, 0);

        var hostIp = string.Join('.', parts[0], parts[1], parts[2], parts[3]);
        return (hostIp, parsed.Port);
    }
}
