namespace SpaceGame.Api.Industry.Planning;

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

internal static class FactionIndustryPlanner
{
    private const float CommodityTargetLevelSeconds = 240f;
    private const float WaterTargetLevelSeconds = 300f;

    internal static IndustryExpansionProject? AnalyzeCommodityNeed(SimulationWorld world, string factionId, string commodityId, bool ignoreActiveExpansionProject = false)
    {
        if (!ignoreActiveExpansionProject && HasActiveExpansionProject(world, factionId))
        {
            return null;
        }

        var bottleneckCommodity = ResolveBottleneckCommodity(world, factionId, commodityId);
        var moduleId = world.ProductionGraph.GetPrimaryProducerModule(bottleneckCommodity);
        if (moduleId is null)
        {
            return null;
        }

        var targetCelestial = SelectFoundationCelestial(world, factionId, bottleneckCommodity);
        if (targetCelestial is null)
        {
            return null;
        }

        var supportStation = SelectSupportStation(world, factionId, moduleId, targetCelestial.SystemId);
        if (supportStation is null)
        {
            return null;
        }

        return new IndustryExpansionProject(
          bottleneckCommodity,
          moduleId,
          targetCelestial.SystemId,
          targetCelestial.Id,
          supportStation.Id);
    }

    internal static IndustryExpansionProject? AnalyzeShipyardNeed(SimulationWorld world, string factionId, bool ignoreActiveExpansionProject = false)
    {
        if (!ignoreActiveExpansionProject && HasActiveExpansionProject(world, factionId))
        {
            return null;
        }

        const string shipyardModuleId = "module_gen_build_l_01";
        if (world.Stations.Any(station =>
            string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
            && station.InstalledModules.Contains(shipyardModuleId, StringComparer.Ordinal)))
        {
            return null;
        }

        if (!world.ModuleRecipes.TryGetValue(shipyardModuleId, out var shipyardRecipe))
        {
            return null;
        }

        var bottleneckCommodity = shipyardRecipe.Inputs
          .Select(input => ResolveBottleneckCommodity(world, factionId, input.ItemId))
          .Distinct(StringComparer.Ordinal)
          .Select(itemId => new
          {
              ItemId = itemId,
              Commodity = FactionEconomyAnalyzer.Build(world, factionId).GetCommodity(itemId),
          })
          .Where(entry => entry.Commodity.ProjectedProductionRatePerSecond <= 0.01f
            || CommodityOperationalSignal.IsStrained(entry.Commodity, GetTargetLevelSeconds(entry.ItemId)))
          .OrderByDescending(entry => entry.Commodity.ProjectedProductionRatePerSecond <= 0.01f ? 1 : 0)
          .ThenByDescending(entry => CommodityOperationalSignal.ComputeNeedScore(entry.Commodity, GetTargetLevelSeconds(entry.ItemId)))
          .ThenBy(entry => entry.Commodity.AvailableStock)
          .Select(entry => entry.ItemId)
          .FirstOrDefault();

        if (!string.IsNullOrWhiteSpace(bottleneckCommodity))
        {
            return AnalyzeCommodityNeed(world, factionId, bottleneckCommodity, ignoreActiveExpansionProject);
        }

        return CreateShipyardFoundationProject(world, factionId, ignoreActiveExpansionProject);
    }

    internal static IndustryExpansionProject? CreateShipyardFoundationProject(SimulationWorld world, string factionId, bool ignoreActiveExpansionProject = false)
    {
        const string shipyardModuleId = "module_gen_build_l_01";
        if (!ignoreActiveExpansionProject && HasActiveExpansionProject(world, factionId))
        {
            return null;
        }

        var targetCelestial = SelectLogisticsFoundationCelestial(world, factionId);
        if (targetCelestial is null)
        {
            return null;
        }

        var supportStation = SelectSupportStation(world, factionId, shipyardModuleId, targetCelestial.SystemId);
        if (supportStation is null)
        {
            return null;
        }

        return new IndustryExpansionProject(
          "shipyard",
          shipyardModuleId,
          targetCelestial.SystemId,
          targetCelestial.Id,
          supportStation.Id);
    }

    internal static IndustryExpansionProject? AnalyzeExpansionNeed(SimulationWorld world, string factionId)
    {
        if (HasActiveExpansionProject(world, factionId))
        {
            return null;
        }

        var bootstrapCommodity = SelectBootstrapCommodity(world, factionId);
        if (bootstrapCommodity is not null)
        {
            var bootstrapModuleId = world.ProductionGraph.GetPrimaryProducerModule(bootstrapCommodity);
            if (bootstrapModuleId is null)
            {
                return null;
            }

            var bootstrapCelestial = SelectFoundationCelestial(world, factionId, bootstrapCommodity);
            if (bootstrapCelestial is null)
            {
                return null;
            }

            var bootstrapSupportStation = SelectSupportStation(world, factionId, bootstrapModuleId, bootstrapCelestial.SystemId);
            if (bootstrapSupportStation is null)
            {
                return null;
            }

            return new IndustryExpansionProject(
              bootstrapCommodity,
              bootstrapModuleId,
              bootstrapCelestial.SystemId,
              bootstrapCelestial.Id,
              bootstrapSupportStation.Id);
        }

        var commodityId = SelectCommodityToExpand(world, factionId);
        if (commodityId is null)
        {
            return null;
        }

        var moduleId = world.ProductionGraph.GetPrimaryProducerModule(commodityId);
        if (moduleId is null)
        {
            return null;
        }

        var targetCelestial = SelectFoundationCelestial(world, factionId, commodityId);
        if (targetCelestial is null)
        {
            return null;
        }

        var supportStation = SelectSupportStation(world, factionId, moduleId, targetCelestial.SystemId);
        if (supportStation is null)
        {
            return null;
        }

        return new IndustryExpansionProject(
          commodityId,
          moduleId,
          targetCelestial.SystemId,
          targetCelestial.Id,
          supportStation.Id);
    }

    internal static IndustryExpansionProject? GetActiveExpansionProject(SimulationWorld world, string factionId)
    {
        var site = world.ConstructionSites.FirstOrDefault(candidate =>
            string.Equals(candidate.FactionId, factionId, StringComparison.Ordinal)
            && string.Equals(candidate.TargetKind, "station-foundation", StringComparison.Ordinal)
            && candidate.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed);
        if (site is null || site.BlueprintId is null)
        {
            return null;
        }

        var supportStationId = world.Stations
          .Where(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal))
          .OrderByDescending(station => station.SystemId == site.SystemId ? 1 : 0)
          .ThenByDescending(station => station.Inventory.Values.Sum())
          .Select(station => station.Id)
          .FirstOrDefault();
        if (supportStationId is null)
        {
            return null;
        }

        return new IndustryExpansionProject(
          site.TargetDefinitionId,
          site.BlueprintId,
          site.SystemId,
          site.CelestialId,
          supportStationId,
          site.Id);
    }

    internal static void EnsureExpansionSite(SimulationWorld world, string factionId, IndustryExpansionProject project)
    {
        if (project.SiteId is not null)
        {
            return;
        }

        if (!CanEstablishExpansionSite(world, factionId, project))
        {
            return;
        }

        var nowUtc = DateTimeOffset.UtcNow;
        var claimId = $"claim-{factionId}-{project.CelestialId}";
        if (world.Claims.All(candidate => candidate.Id != claimId))
        {
            world.Claims.Add(new ClaimRuntime
            {
                Id = claimId,
                FactionId = factionId,
                SystemId = project.SystemId,
                CelestialId = project.CelestialId,
                PlacedAtUtc = nowUtc,
                ActivatesAtUtc = nowUtc.AddSeconds(8),
                State = ClaimStateKinds.Activating,
                Health = 100f,
            });
        }

        if (!world.ModuleRecipes.TryGetValue(project.ModuleId, out var recipe))
        {
            return;
        }

        var siteId = $"site-{factionId}-{project.CelestialId}";
        if (world.ConstructionSites.Any(candidate => candidate.Id == siteId))
        {
            return;
        }

        var site = new ConstructionSiteRuntime
        {
            Id = siteId,
            FactionId = factionId,
            SystemId = project.SystemId,
            CelestialId = project.CelestialId,
            TargetKind = "station-foundation",
            TargetDefinitionId = project.CommodityId,
            BlueprintId = project.ModuleId,
            ClaimId = claimId,
            StationId = null,
            State = ConstructionSiteStateKinds.Planned,
        };

        foreach (var input in recipe.Inputs)
        {
            site.RequiredItems[input.ItemId] = input.Amount;
            site.DeliveredItems[input.ItemId] = 0f;
            var orderId = $"market-order-{site.Id}-{input.ItemId}";
            site.MarketOrderIds.Add(orderId);
            world.MarketOrders.Add(new MarketOrderRuntime
            {
                Id = orderId,
                FactionId = factionId,
                StationId = project.SupportStationId,
                ConstructionSiteId = site.Id,
                Kind = MarketOrderKinds.Buy,
                ItemId = input.ItemId,
                Amount = input.Amount,
                RemainingAmount = input.Amount,
                Valuation = 1.1f,
                State = MarketOrderStateKinds.Open,
            });
        }

        if (world.Stations.FirstOrDefault(station => station.Id == project.SupportStationId) is { } supportStation)
        {
            foreach (var orderId in site.MarketOrderIds)
            {
                supportStation.MarketOrderIds.Add(orderId);
            }
        }

        world.ConstructionSites.Add(site);
    }

    private static string? SelectCommodityToExpand(SimulationWorld world, string factionId)
    {
        var demandByItem = world.MarketOrders
          .Where(order =>
              string.Equals(order.FactionId, factionId, StringComparison.Ordinal)
              && string.Equals(order.Kind, MarketOrderKinds.Buy, StringComparison.Ordinal)
              && order.RemainingAmount > 0.01f)
          .GroupBy(order => order.ItemId, StringComparer.Ordinal)
          .ToDictionary(group => group.Key, group => group.Sum(order => order.RemainingAmount), StringComparer.Ordinal);

        var threatAssessment = CommanderPlanningService.FindFactionThreatAssessment(world, factionId);
        if (threatAssessment is not null && threatAssessment.EnemyFactionCount > 0)
        {
            demandByItem["hullparts"] = demandByItem.GetValueOrDefault("hullparts") + 120f;
            demandByItem["claytronics"] = demandByItem.GetValueOrDefault("claytronics") + 90f;
        }

        return demandByItem
          .Select(entry =>
          {
              var itemId = ResolveBottleneckCommodity(world, factionId, entry.Key);
              var commodity = FactionEconomyAnalyzer.Build(world, factionId).GetCommodity(itemId);
              var score = entry.Value + CommodityOperationalSignal.ComputeNeedScore(commodity, GetTargetLevelSeconds(itemId));
              return (ItemId: itemId, Score: score);
          })
          .Where(entry => entry.ItemId is not null)
          .GroupBy(entry => entry.ItemId!, StringComparer.Ordinal)
          .Select(group => (ItemId: group.Key, Score: group.Sum(entry => entry.Score)))
          .OrderByDescending(entry => entry.Score)
          .Select(entry => entry.ItemId)
          .FirstOrDefault();
    }

    private static string? SelectBootstrapCommodity(SimulationWorld world, string factionId)
    {
        if (!FactionHasProducerForCommodity(world, factionId, "refinedmetals"))
        {
            return "refinedmetals";
        }

        return null;
    }

    private static string ResolveBottleneckCommodity(SimulationWorld world, string factionId, string itemId)
    {
        var visited = new HashSet<string>(StringComparer.Ordinal);
        return ResolveBottleneckCommodity(world, factionId, itemId, visited);
    }

    private static string ResolveBottleneckCommodity(SimulationWorld world, string factionId, string itemId, HashSet<string> visited)
    {
        if (!visited.Add(itemId))
        {
            return itemId;
        }

        var producers = world.ProductionGraph.GetProcessesForOutput(itemId);
        if (producers.Count == 0)
        {
            return itemId;
        }

        var hasFactionProducer = producers
          .SelectMany(process => process.RequiredModuleIds)
          .Any(moduleId => world.Stations.Any(station =>
              string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
              && station.InstalledModules.Contains(moduleId, StringComparer.Ordinal)));
        if (!hasFactionProducer)
        {
            return itemId;
        }

        var weakestUnproducedInput = world.ProductionGraph.GetImmediateInputs(itemId)
          .Where(inputId => !FactionHasProducerForCommodity(world, factionId, inputId))
          .Select(inputId =>
          {
              var commodity = FactionEconomyAnalyzer.Build(world, factionId).GetCommodity(inputId);
              return (ItemId: inputId, Score: CommodityOperationalSignal.ComputeNeedScore(commodity, GetTargetLevelSeconds(inputId)), Stockpile: commodity.AvailableStock);
          })
          .OrderByDescending(entry => entry.Score)
          .ThenBy(entry => entry.Stockpile)
          .FirstOrDefault();

        if (!string.IsNullOrWhiteSpace(weakestUnproducedInput.ItemId)
            && (weakestUnproducedInput.Score > 0.01f || weakestUnproducedInput.Stockpile < 120f))
        {
            return ResolveBottleneckCommodity(world, factionId, weakestUnproducedInput.ItemId, visited);
        }

        var weakestInput = world.ProductionGraph.GetImmediateInputs(itemId)
          .Select(inputId =>
          {
              var commodity = FactionEconomyAnalyzer.Build(world, factionId).GetCommodity(inputId);
              return (ItemId: inputId, Score: CommodityOperationalSignal.ComputeNeedScore(commodity, GetTargetLevelSeconds(inputId)));
          })
          .OrderByDescending(entry => entry.Score)
          .FirstOrDefault();

        return weakestInput.Score > GetCommodityNeedScore(world, factionId, itemId) * 0.6f
          ? ResolveBottleneckCommodity(world, factionId, weakestInput.ItemId, visited)
          : itemId;
    }

    internal static bool FactionHasProducerForCommodity(SimulationWorld world, string factionId, string itemId)
        => FactionEconomyAnalyzer.Build(world, factionId).GetCommodity(itemId).ProjectedProductionRatePerSecond > 0.01f;

    internal static IReadOnlyCollection<string> ResolveRootResourceItems(SimulationWorld world, string commodityId)
    {
        var frontier = new Queue<string>();
        var resources = new HashSet<string>(StringComparer.Ordinal);
        var visited = new HashSet<string>(StringComparer.Ordinal);
        frontier.Enqueue(commodityId);

        while (frontier.Count > 0)
        {
            var current = frontier.Dequeue();
            if (!visited.Add(current))
            {
                continue;
            }

            var inputs = world.ProductionGraph.GetImmediateInputs(current);
            if (inputs.Count == 0)
            {
                resources.Add(current);
                continue;
            }

            foreach (var input in inputs)
            {
                frontier.Enqueue(input);
            }
        }

        return resources.Count > 0 ? resources : [commodityId];
    }

    private static bool HasActiveExpansionProject(SimulationWorld world, string factionId) =>
        world.ConstructionSites.Any(site =>
            string.Equals(site.FactionId, factionId, StringComparison.Ordinal)
            && string.Equals(site.TargetKind, "station-foundation", StringComparison.Ordinal)
            && site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed);

    private static float GetCommodityNeedScore(SimulationWorld world, string factionId, string itemId)
    {
        var commodity = FactionEconomyAnalyzer.Build(world, factionId).GetCommodity(itemId);
        return CommodityOperationalSignal.ComputeNeedScore(commodity, GetTargetLevelSeconds(itemId));
    }

    private static float GetTargetLevelSeconds(string itemId) =>
      string.Equals(itemId, "water", StringComparison.Ordinal) ? WaterTargetLevelSeconds : CommodityTargetLevelSeconds;

    private static CelestialRuntime? SelectFoundationCelestial(SimulationWorld world, string factionId, string commodityId)
    {
        var resourceItems = ResolveRootResourceItems(world, commodityId);
        return world.Celestials
          .Where(celestial =>
              celestial.Kind == SpatialNodeKind.LagrangePoint
              && celestial.OccupyingStructureId is null
              && world.Claims.All(claim => claim.CelestialId != celestial.Id || claim.State == ClaimStateKinds.Destroyed)
              && IsExpansionSystemEligible(world, factionId, celestial.SystemId))
          .OrderByDescending(celestial => ScoreCelestial(world, factionId, celestial, resourceItems))
          .FirstOrDefault();
    }

    private static CelestialRuntime? SelectLogisticsFoundationCelestial(SimulationWorld world, string factionId)
    {
        return world.Celestials
          .Where(celestial =>
              celestial.Kind == SpatialNodeKind.LagrangePoint
              && celestial.OccupyingStructureId is null
              && world.Claims.All(claim => claim.CelestialId != celestial.Id || claim.State == ClaimStateKinds.Destroyed)
              && IsExpansionSystemEligible(world, factionId, celestial.SystemId))
          .OrderByDescending(celestial => world.Stations.Count(station =>
              string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
              && string.Equals(station.SystemId, celestial.SystemId, StringComparison.Ordinal)))
          .ThenByDescending(celestial => world.Stations
              .Where(station =>
                  string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
                  && string.Equals(station.SystemId, celestial.SystemId, StringComparison.Ordinal))
              .Sum(station => station.Inventory.Values.Sum()))
          .FirstOrDefault();
    }

    private static float ScoreCelestial(SimulationWorld world, string factionId, CelestialRuntime celestial, IReadOnlyCollection<string> resourceItems)
    {
        var resourceScore = world.Nodes
          .Where(node => node.SystemId == celestial.SystemId && resourceItems.Contains(node.ItemId, StringComparer.Ordinal))
          .Sum(node => node.OreRemaining);
        var factionPresence = world.Stations.Count(station =>
            string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
            && string.Equals(station.SystemId, celestial.SystemId, StringComparison.Ordinal));
        var controlState = GeopoliticalSimulationService.GetSystemControlState(world, celestial.SystemId);
        var region = GeopoliticalSimulationService.GetPrimaryEconomicRegion(world, factionId, celestial.SystemId);
        var strategicProfile = world.Geopolitics?.Territory.StrategicProfiles.FirstOrDefault(profile => profile.SystemId == celestial.SystemId);
        var pressure = world.Geopolitics?.Territory.Pressures
          .Where(entry => entry.SystemId == celestial.SystemId && entry.FactionId == factionId)
          .OrderByDescending(entry => entry.HostileInfluence)
          .ThenBy(entry => entry.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        var controlBias = string.Equals(controlState?.ControllerFactionId, factionId, StringComparison.Ordinal)
          ? 12_000f
          : string.Equals(controlState?.PrimaryClaimantFactionId, factionId, StringComparison.Ordinal)
            ? 4_000f
            : 0f;
        var regionBias = region is null
          ? 0f
          : region.Kind switch
          {
              "core-industry" => 4_500f,
              "shipbuilding" => 3_250f,
              "trade-hub" => 2_250f,
              "corridor" => 1_500f,
              _ => 1_000f,
          };
        var securityPenalty = ((pressure?.HostileInfluence ?? 0f) * 14f)
          + ((strategicProfile?.TerritorialPressure ?? 0f) * 9f)
          + ((world.Geopolitics is null ? 0f : GeopoliticalSimulationService.GetSystemRouteRisk(world, celestial.SystemId, factionId)) * 250f);
        return resourceScore
          + (factionPresence * 5_000f)
          + controlBias
          + regionBias
          - securityPenalty;
    }

    private static bool IsExpansionSystemEligible(SimulationWorld world, string factionId, string systemId)
    {
        var controlState = GeopoliticalSimulationService.GetSystemControlState(world, systemId);
        if (controlState is null)
        {
            return true;
        }

        var authorityFactionId = controlState.ControllerFactionId ?? controlState.PrimaryClaimantFactionId;
        if (authorityFactionId is null || string.Equals(authorityFactionId, factionId, StringComparison.Ordinal))
        {
            return true;
        }

        if (!GeopoliticalSimulationService.HasMilitaryAccess(world, factionId, authorityFactionId))
        {
            return false;
        }

        return !GeopoliticalSimulationService.HasHostileRelation(world, factionId, authorityFactionId);
    }

    private static bool CanEstablishExpansionSite(SimulationWorld world, string factionId, IndustryExpansionProject project)
    {
        if (!IsExpansionSystemEligible(world, factionId, project.SystemId))
        {
            return false;
        }

        var controlState = GeopoliticalSimulationService.GetSystemControlState(world, project.SystemId);
        if (controlState?.IsContested == true)
        {
            return false;
        }

        var pressure = world.Geopolitics?.Territory.Pressures
          .Where(entry => entry.SystemId == project.SystemId && entry.FactionId == factionId)
          .OrderByDescending(entry => entry.CorridorRisk + entry.HostileInfluence)
          .ThenBy(entry => entry.Id, StringComparer.Ordinal)
          .FirstOrDefault();
        return pressure is null || (pressure.CorridorRisk < 0.8f && pressure.HostileInfluence < 1.2f);
    }

    private static StationRuntime? SelectSupportStation(SimulationWorld world, string factionId, string moduleId, string targetSystemId)
    {
        var constructionInputs = world.ModuleRecipes.TryGetValue(moduleId, out var recipe)
          ? recipe.Inputs.Select(input => input.ItemId).ToList()
          : [];

        return world.Stations
          .Where(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal))
          .OrderByDescending(station => station.SystemId == targetSystemId ? 1 : 0)
          .ThenByDescending(station => constructionInputs.Sum(inputId => GetInventoryAmount(station.Inventory, inputId)))
          .ThenByDescending(station => station.Inventory.Values.Sum())
          .FirstOrDefault();
    }
}

internal sealed record IndustryExpansionProject(
  string CommodityId,
  string ModuleId,
  string SystemId,
  string CelestialId,
  string SupportStationId,
  string? SiteId = null);
