using Orleans.Runtime.Placement;

namespace Managing.Application.Orleans;

/// <summary>
/// Placement strategy for compute-intensive grains (Genetic Backtest, Bundle Backtest)
/// These grains should be placed on servers with the "Compute" role
/// </summary>
[Serializable]
public sealed class ComputePlacementStrategy : PlacementStrategy
{
}

/// <summary>
/// Placement strategy for trading grains (Live Trading Bots, Agent Grains)
/// These grains should be placed on servers with the "Trading" role
/// Uses activation-count-based placement for load balancing across multiple trading silos
/// </summary>
[Serializable]
public sealed class TradingPlacementStrategy : PlacementStrategy
{
}

/// <summary>
/// Placement attribute for compute-intensive grains
/// Uses custom placement strategy with random fallback
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class ComputePlacementAttribute : Attribute
{
}

/// <summary>
/// Placement attribute for trading grains with load balancing
/// Uses custom placement strategy with activation-count-based fallback
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class TradingPlacementAttribute : Attribute
{
}

/// <summary>
/// Placement director for compute-intensive grains
/// Places grains on silos with the "Compute" role, falls back to random placement
/// </summary>
public class ComputePlacementDirector : IPlacementDirector
{
    public Task<SiloAddress> OnAddActivation(
        PlacementStrategy strategy,
        PlacementTarget target,
        IPlacementContext context)
    {
        var compatibleSilos = context.GetCompatibleSilos(target).ToList();

        if (!compatibleSilos.Any())
        {
            throw new InvalidOperationException(
                $"No compatible silos available for compute grain {target.GrainIdentity}");
        }

        // Try to find silos with "Compute" role by checking silo names
        var computeSilos = compatibleSilos.Where(silo =>
        {
            // Check if silo name contains "-Compute" to identify compute silos
            var siloName = GetSiloName(silo);
            return siloName?.Contains("-Compute") == true;
        }).ToList();

        if (computeSilos.Any())
        {
            // Use random selection among compute silos
            var random = new Random();
            return Task.FromResult(computeSilos[random.Next(computeSilos.Count)]);
        }

        // Fallback: Use random placement among all compatible silos
        var randomFallback = new Random();
        return Task.FromResult(compatibleSilos[randomFallback.Next(compatibleSilos.Count)]);
    }

    private string GetSiloName(SiloAddress siloAddress)
    {
        // Extract silo name from the address - this is a simplified approach
        // In a real implementation, you might need to access silo metadata differently
        return siloAddress.ToString();
    }
}

/// <summary>
/// Placement director for trading grains with load balancing
/// Places grains on silos with the "Trading" role using activation-count-based placement
/// Falls back to activation-count-based placement if no trading silos found
/// </summary>
public class TradingPlacementDirector : IPlacementDirector
{
    public Task<SiloAddress> OnAddActivation(
        PlacementStrategy strategy,
        PlacementTarget target,
        IPlacementContext context)
    {
        var compatibleSilos = context.GetCompatibleSilos(target).ToList();

        if (!compatibleSilos.Any())
        {
            throw new InvalidOperationException(
                $"No compatible silos available for trading grain {target.GrainIdentity}");
        }

        // Try to find silos with "Trading" role by checking silo names
        var tradingSilos = compatibleSilos.Where(silo =>
        {
            // Check if silo name contains "-Trading" to identify trading silos
            var siloName = GetSiloName(silo);
            return siloName?.Contains("-Trading") == true;
        }).ToList();

        if (tradingSilos.Any())
        {
            // Use activation-count-based placement among trading silos
            return SelectSiloWithLoadBalancing(tradingSilos, context);
        }

        // Fallback: Use activation-count-based placement among all compatible silos
        return SelectSiloWithLoadBalancing(compatibleSilos, context);
    }

    private Task<SiloAddress> SelectSiloWithLoadBalancing(List<SiloAddress> silos, IPlacementContext context)
    {
        if (silos.Count == 1)
        {
            return Task.FromResult(silos.First());
        }

        // Implement "Power of Two Choices" algorithm for load balancing
        var random = new Random();
        var selectedSilos = silos.OrderBy(x => random.Next()).Take(2).ToList();

        // For now, use random selection between the two chosen silos
        // In a real implementation, you would check activation counts
        var selectedSilo = selectedSilos[random.Next(selectedSilos.Count)];

        return Task.FromResult(selectedSilo);
    }

    private string GetSiloName(SiloAddress siloAddress)
    {
        // Extract silo name from the address - this is a simplified approach
        // In a real implementation, you might need to access silo metadata differently
        return siloAddress.ToString();
    }
}