namespace SpaceGame.Api.Industry.Planning;

using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;

internal sealed class FactionEconomySnapshot
{
    private readonly Dictionary<string, FactionCommoditySnapshot> commodities = new(StringComparer.Ordinal);

    internal IReadOnlyDictionary<string, FactionCommoditySnapshot> Commodities => commodities;

    internal FactionCommoditySnapshot GetCommodity(string itemId)
    {
        if (!commodities.TryGetValue(itemId, out var commodity))
        {
            commodity = new FactionCommoditySnapshot(itemId);
            commodities[itemId] = commodity;
        }

        return commodity;
    }
}

internal sealed class FactionCommoditySnapshot
{
    internal FactionCommoditySnapshot(string itemId)
    {
        ItemId = itemId;
    }

    internal string ItemId { get; }
    internal float OnHand { get; set; }
    internal float ReservedForConstruction { get; set; }
    internal float BuyBacklog { get; set; }
    internal float SellBacklog { get; set; }
    internal float Inbound { get; set; }
    internal float ProductionRatePerSecond { get; set; }
    internal float CommittedProductionRatePerSecond { get; set; }
    internal float ConsumptionRatePerSecond { get; set; }

    internal float AvailableStock => MathF.Max(0f, OnHand + Inbound - ReservedForConstruction);
    internal float NetRatePerSecond => ProductionRatePerSecond - ConsumptionRatePerSecond;
    internal float ProjectedProductionRatePerSecond => ProductionRatePerSecond + CommittedProductionRatePerSecond;
    internal float ProjectedNetRatePerSecond => ProjectedProductionRatePerSecond - ConsumptionRatePerSecond;
    internal float OperationalUsageRatePerSecond => MathF.Max(ConsumptionRatePerSecond, BuyBacklog / 180f);
    internal float LevelSeconds => AvailableStock <= 0.01f
        ? 0f
        : AvailableStock / MathF.Max(OperationalUsageRatePerSecond, 0.01f);

    internal CommodityLevelKind Level =>
        LevelSeconds switch
        {
            <= 60f => CommodityLevelKind.Critical,
            <= 180f => CommodityLevelKind.Low,
            <= 480f => CommodityLevelKind.Stable,
            _ => CommodityLevelKind.Surplus,
        };
}

internal enum CommodityLevelKind
{
    Critical,
    Low,
    Stable,
    Surplus,
}

internal static class FactionEconomyAnalyzer
{
    internal static FactionEconomySnapshot Build(SimulationWorld world, string factionId)
    {
        var snapshot = new FactionEconomySnapshot();

        foreach (var station in world.Stations.Where(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal)))
        {
            foreach (var (itemId, amount) in station.Inventory)
            {
                snapshot.GetCommodity(itemId).OnHand += amount;
            }

            foreach (var laneKey in StationSimulationService.GetStationProductionLanes(world, station))
            {
                var recipe = StationSimulationService.SelectProductionRecipe(world, station, laneKey);
                if (recipe is null)
                {
                    continue;
                }

                var throughput = StationSimulationService.GetStationProductionThroughput(world, station, recipe);
                var cyclesPerSecond = (station.WorkforceEffectiveRatio * throughput) / MathF.Max(recipe.Duration, 0.01f);
                if (cyclesPerSecond <= 0.0001f)
                {
                    continue;
                }

                foreach (var input in recipe.Inputs)
                {
                    snapshot.GetCommodity(input.ItemId).ConsumptionRatePerSecond += input.Amount * cyclesPerSecond;
                }

                foreach (var output in recipe.Outputs)
                {
                    snapshot.GetCommodity(output.ItemId).ProductionRatePerSecond += output.Amount * cyclesPerSecond;
                }
            }
        }

        foreach (var order in world.MarketOrders.Where(order =>
            string.Equals(order.FactionId, factionId, StringComparison.Ordinal)
            && order.State != MarketOrderStateKinds.Cancelled
            && order.RemainingAmount > 0.01f))
        {
            var commodity = snapshot.GetCommodity(order.ItemId);
            if (string.Equals(order.Kind, MarketOrderKinds.Buy, StringComparison.Ordinal))
            {
                commodity.BuyBacklog += order.RemainingAmount;
            }
            else if (string.Equals(order.Kind, MarketOrderKinds.Sell, StringComparison.Ordinal))
            {
                commodity.SellBacklog += order.RemainingAmount;
            }
        }

        foreach (var site in world.ConstructionSites.Where(site =>
            string.Equals(site.FactionId, factionId, StringComparison.Ordinal)
            && site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed))
        {
            ApplyCommittedProduction(world, snapshot, site);

            foreach (var required in site.RequiredItems)
            {
                var remaining = MathF.Max(0f, required.Value - GetConstructionDeliveredAmount(world, site, required.Key));
                if (remaining > 0.01f)
                {
                    snapshot.GetCommodity(required.Key).ReservedForConstruction += remaining;
                }
            }
        }

        return snapshot;
    }

    private static void ApplyCommittedProduction(
        SimulationWorld world,
        FactionEconomySnapshot snapshot,
        ConstructionSiteRuntime site)
    {
        if (string.IsNullOrWhiteSpace(site.BlueprintId)
            || !world.ModuleRecipes.TryGetValue(site.BlueprintId, out var recipe))
        {
            return;
        }

        var recipeOutputs = world.Recipes.Values
          .Where(candidate => string.Equals(StationSimulationService.GetStationProductionLaneKey(world, candidate), site.BlueprintId, StringComparison.Ordinal))
          .SelectMany(candidate => candidate.Outputs)
          .GroupBy(output => output.ItemId, StringComparer.Ordinal)
          .ToDictionary(group => group.Key, group => group.Sum(output => output.Amount), StringComparer.Ordinal);
        if (recipeOutputs.Count == 0)
        {
            return;
        }

        var materialFraction = 0f;
        var materialTerms = 0;
        foreach (var required in site.RequiredItems)
        {
            materialTerms += 1;
            materialFraction += required.Value <= 0.01f
              ? 1f
              : Math.Clamp(GetConstructionDeliveredAmount(world, site, required.Key) / required.Value, 0f, 1f);
        }

        materialFraction = materialTerms == 0 ? 1f : materialFraction / materialTerms;

        var buildFraction = recipe.Duration <= 0.01f
          ? 0f
          : Math.Clamp(site.Progress / recipe.Duration, 0f, 1f);
        var readiness = site.State switch
        {
            ConstructionSiteStateKinds.Active => 0.3f,
            ConstructionSiteStateKinds.Planned => 0.15f,
            _ => 0f,
        };

        readiness += materialFraction * 0.45f;
        readiness += buildFraction * 0.25f;

        if (site.AssignedConstructorShipIds.Count > 0)
        {
            readiness += 0.1f;
        }

        readiness = Math.Clamp(readiness, 0f, 1f);
        if (readiness <= 0.01f)
        {
            return;
        }

        var cyclesPerSecond = readiness / MathF.Max(recipe.Duration, 0.01f);
        foreach (var (productItemId, amount) in recipeOutputs)
        {
            snapshot.GetCommodity(productItemId).CommittedProductionRatePerSecond += amount * cyclesPerSecond;
        }
    }
}
