namespace ScadaLink.Host;

/// <summary>
/// Validates required configuration before Akka.NET actor system creation.
/// Runs early in startup to fail fast with clear error messages.
/// </summary>
public static class StartupValidator
{
    public static void Validate(IConfiguration configuration)
    {
        var errors = new List<string>();

        var nodeSection = configuration.GetSection("ScadaLink:Node");
        var role = nodeSection["Role"];
        if (string.IsNullOrEmpty(role) || (role != "Central" && role != "Site"))
            errors.Add("ScadaLink:Node:Role must be 'Central' or 'Site'");

        if (string.IsNullOrEmpty(nodeSection["NodeHostname"]))
            errors.Add("ScadaLink:Node:NodeHostname is required");

        var portStr = nodeSection["RemotingPort"];
        if (!int.TryParse(portStr, out var port) || port < 1 || port > 65535)
            errors.Add("ScadaLink:Node:RemotingPort must be 1-65535");

        if (role == "Site" && string.IsNullOrEmpty(nodeSection["SiteId"]))
            errors.Add("ScadaLink:Node:SiteId is required for Site nodes");

        if (role == "Central")
        {
            var dbSection = configuration.GetSection("ScadaLink:Database");
            if (string.IsNullOrEmpty(dbSection["ConfigurationDb"]))
                errors.Add("ScadaLink:Database:ConfigurationDb connection string required for Central");
            if (string.IsNullOrEmpty(dbSection["MachineDataDb"]))
                errors.Add("ScadaLink:Database:MachineDataDb connection string required for Central");

            var secSection = configuration.GetSection("ScadaLink:Security");
            if (string.IsNullOrEmpty(secSection["LdapServer"]))
                errors.Add("ScadaLink:Security:LdapServer required for Central");
            if (string.IsNullOrEmpty(secSection["JwtSigningKey"]))
                errors.Add("ScadaLink:Security:JwtSigningKey required for Central");
        }

        var seedNodes = configuration.GetSection("ScadaLink:Cluster:SeedNodes").Get<List<string>>();
        if (seedNodes == null || seedNodes.Count < 2)
            errors.Add("ScadaLink:Cluster:SeedNodes must have at least 2 entries");

        if (role == "Site")
        {
            var grpcPortStr = nodeSection["GrpcPort"];
            int grpcPort = 8083; // NodeOptions default when the key is absent
            if (grpcPortStr != null && (!int.TryParse(grpcPortStr, out grpcPort) || grpcPort < 1 || grpcPort > 65535))
                errors.Add("ScadaLink:Node:GrpcPort must be 1-65535");

            var dbSection = configuration.GetSection("ScadaLink:Database");
            if (string.IsNullOrEmpty(dbSection["SiteDbPath"]))
                errors.Add("ScadaLink:Database:SiteDbPath required for Site nodes");

            // Host-004: a seed node must reference an Akka.Remote endpoint, never the
            // Kestrel HTTP/2 gRPC port. A seed entry whose port equals this node's
            // GrpcPort would make a joining node attempt an Akka.Remote TCP
            // association against the gRPC listener and fail.
            if (seedNodes != null)
            {
                foreach (var seed in seedNodes)
                {
                    if (SeedNodePort(seed) == grpcPort)
                        errors.Add(
                            $"ScadaLink:Cluster:SeedNodes entry '{seed}' must not target the gRPC port " +
                            $"({grpcPort}); seed nodes must reference Akka remoting ports");
                }
            }
        }

        if (errors.Count > 0)
            throw new InvalidOperationException(
                $"Configuration validation failed:\n{string.Join("\n", errors.Select(e => $"  - {e}"))}");
    }

    /// <summary>
    /// Extracts the TCP port from an Akka seed-node address of the form
    /// <c>akka.tcp://system@host:port</c>. Returns <c>-1</c> when no port can be parsed.
    /// </summary>
    private static int SeedNodePort(string seedNode)
    {
        if (string.IsNullOrWhiteSpace(seedNode))
            return -1;

        var lastColon = seedNode.LastIndexOf(':');
        if (lastColon < 0 || lastColon == seedNode.Length - 1)
            return -1;

        return int.TryParse(seedNode[(lastColon + 1)..], out var port) ? port : -1;
    }
}
