// Ported from Go accounts.go:1500-2000 — cycle detection for service imports.

using NATS.Server.Imports;

namespace NATS.Server.Auth;

/// <summary>
/// Provides cycle detection and validation for cross-account service imports.
/// Go reference: accounts.go checkForImportCycle / addServiceImport.
/// </summary>
public static class AccountImportExport
{
    /// <summary>
    /// DFS through the service import graph to detect cycles.
    /// Returns true if following service imports from <paramref name="from"/>
    /// eventually leads back to <paramref name="to"/>.
    /// </summary>
    public static bool DetectCycle(Account from, Account to, HashSet<string>? visited = null)
    {
        ArgumentNullException.ThrowIfNull(from);
        ArgumentNullException.ThrowIfNull(to);

        visited ??= new HashSet<string>(StringComparer.Ordinal);

        if (!visited.Add(from.Name))
            return false; // Already visited, no new cycle found from this node

        // Walk all service imports from the 'from' account
        foreach (var kvp in from.Imports.Services)
        {
            foreach (var serviceImport in kvp.Value)
            {
                var dest = serviceImport.DestinationAccount;

                // Direct cycle: import destination is the target account
                if (string.Equals(dest.Name, to.Name, StringComparison.Ordinal))
                    return true;

                // Indirect cycle: recursively check if destination leads back to target
                if (DetectCycle(dest, to, visited))
                    return true;
            }
        }

        return false;
    }

    /// <summary>
    /// Validates that the import is authorized and does not create a cycle.
    /// </summary>
    /// <exception cref="UnauthorizedAccessException">Thrown when the importing account is not authorized.</exception>
    /// <exception cref="InvalidOperationException">Thrown when the import would create a cycle.</exception>
    public static void ValidateImport(Account importingAccount, Account exportingAccount, string exportSubject)
    {
        ArgumentNullException.ThrowIfNull(importingAccount);
        ArgumentNullException.ThrowIfNull(exportingAccount);

        // Check authorization first
        if (exportingAccount.Exports.Services.TryGetValue(exportSubject, out var export))
        {
            if (!export.Auth.IsAuthorized(importingAccount))
                throw new UnauthorizedAccessException(
                    $"Account '{importingAccount.Name}' not authorized to import '{exportSubject}' from '{exportingAccount.Name}'");
        }
        else
        {
            throw new InvalidOperationException(
                $"No service export found for '{exportSubject}' on account '{exportingAccount.Name}'");
        }

        // Check for cycles: would importing from exportingAccount create a cycle
        // back to importingAccount?
        if (DetectCycle(exportingAccount, importingAccount))
            throw new InvalidOperationException("Import would create a cycle");
    }
}
