using SpaceGame.Api.Universe.Bootstrap;
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
using static SpaceGame.Api.Universe.Scenario.LoaderSupport;

namespace SpaceGame.Api.Universe.Scenario;

public sealed class WorldSeedingService(IStaticDataProvider staticData)
{
    internal List<FactionRuntime> CreateFactions(
      IReadOnlyCollection<StationRuntime> stations,
      IReadOnlyCollection<ShipRuntime> ships)
    {
        var factionIds = stations
          .Select(station => station.FactionId)
          .Concat(ships.Select(ship => ship.FactionId))
          .Where(factionId => !string.IsNullOrWhiteSpace(factionId))
          .Distinct(StringComparer.Ordinal)
          .OrderBy(factionId => factionId, StringComparer.Ordinal)
          .ToList();

        if (factionIds.Count == 0)
        {
            return [];
        }

        return factionIds.Select(CreateFaction).ToList();
    }

    internal void BootstrapFactionEconomy(
      IReadOnlyCollection<FactionRuntime> factions,
      IReadOnlyCollection<StationRuntime> stations)
    {
        foreach (var faction in factions)
        {
            faction.Credits = MathF.Max(faction.Credits, MinimumFactionCredits);

            var ownedStations = stations
              .Where(station => station.FactionId == faction.Id)
              .ToList();

            var refineries = ownedStations
              .Where(station => string.Equals(StationSimulationService.DetermineStationRole(station), "refinery", StringComparison.Ordinal))
              .ToList();

            if (refineries.Count > 0)
            {
                foreach (var refinery in refineries)
                {
                    refinery.Inventory["refinedmetals"] = MathF.Max(GetInventoryAmount(refinery.Inventory, "refinedmetals"), MinimumRefineryStock);
                }

                if (refineries.All(station => GetInventoryAmount(station.Inventory, "ore") < MinimumRefineryOre))
                {
                    refineries[0].Inventory["ore"] = MinimumRefineryOre;
                }
            }

            foreach (var shipyard in ownedStations.Where(station => HasInstalledModules(station, "module_gen_build_l_01")))
            {
                shipyard.Inventory["refinedmetals"] = MathF.Max(GetInventoryAmount(shipyard.Inventory, "refinedmetals"), MinimumShipyardStock);
            }
        }
    }

    internal void InitializeStationStockpiles(
      IReadOnlyCollection<StationRuntime> stations,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
    {
        foreach (var station in stations)
        {
            InitializeStationPopulation(station, moduleDefinitions);
        }
    }

    internal List<ClaimRuntime> CreateClaims(
      IReadOnlyCollection<StationRuntime> stations,
      IReadOnlyCollection<CelestialRuntime> celestials,
      DateTimeOffset nowUtc)
    {
        var stationsByCelestialId = stations
          .Where(station => station.CelestialId is not null)
          .ToDictionary(station => station.CelestialId!, StringComparer.Ordinal);
        var claims = new List<ClaimRuntime>();

        foreach (var celestial in celestials.Where(c => c.Kind == SpatialNodeKind.LagrangePoint))
        {
            if (!stationsByCelestialId.TryGetValue(celestial.Id, out var station))
            {
                continue;
            }

            claims.Add(new ClaimRuntime
            {
                Id = $"claim-{celestial.Id}",
                FactionId = station.FactionId,
                SystemId = celestial.SystemId,
                CelestialId = celestial.Id,
                PlacedAtUtc = nowUtc,
                ActivatesAtUtc = nowUtc.AddSeconds(8),
                State = ClaimStateKinds.Activating,
                Health = 100f,
            });
        }

        return claims;
    }

    internal (List<ConstructionSiteRuntime> ConstructionSites, List<MarketOrderRuntime> MarketOrders) CreateConstructionSites(
      SimulationWorld world)
    {
        var sites = new List<ConstructionSiteRuntime>();
        var orders = new List<MarketOrderRuntime>();

        foreach (var station in world.Stations)
        {
            if (HasSatisfiedStarterObjectiveLayout(world, station))
            {
                continue;
            }

            var moduleId = InfrastructureSimulationService.GetNextStationModuleToBuild(station, world);
            if (moduleId is null || station.CelestialId is null)
            {
                continue;
            }

            var claim = world.Claims.FirstOrDefault(candidate => candidate.CelestialId == station.CelestialId);
            if (claim is null || !world.ModuleRecipes.TryGetValue(moduleId, out var recipe))
            {
                continue;
            }

            var site = new ConstructionSiteRuntime
            {
                Id = $"site-{station.Id}",
                FactionId = station.FactionId,
                SystemId = station.SystemId,
                CelestialId = station.CelestialId,
                TargetKind = "station-module",
                TargetDefinitionId = "station",
                BlueprintId = moduleId,
                ClaimId = claim.Id,
                StationId = station.Id,
                State = claim.State == ClaimStateKinds.Active ? ConstructionSiteStateKinds.Active : ConstructionSiteStateKinds.Planned,
            };

            foreach (var input in recipe.Inputs)
            {
                site.RequiredItems[input.ItemId] = input.Amount;
                site.DeliveredItems[input.ItemId] = 0f;

                var orderId = $"market-order-{station.Id}-{moduleId}-{input.ItemId}";
                site.MarketOrderIds.Add(orderId);
                station.MarketOrderIds.Add(orderId);
                orders.Add(new MarketOrderRuntime
                {
                    Id = orderId,
                    FactionId = station.FactionId,
                    StationId = station.Id,
                    ConstructionSiteId = site.Id,
                    Kind = MarketOrderKinds.Buy,
                    ItemId = input.ItemId,
                    Amount = input.Amount,
                    RemainingAmount = input.Amount,
                    Valuation = 1f,
                    State = MarketOrderStateKinds.Open,
                });
            }

            sites.Add(site);
        }

        return (sites, orders);
    }

    private static bool HasSatisfiedStarterObjectiveLayout(SimulationWorld world, StationRuntime station)
    {
        var role = StationSimulationService.DetermineStationRole(station);
        var objectiveModuleId = StarterStationLayoutResolver.ResolveObjectiveModuleId(role, station.FactionId, world.ModuleDefinitions);

        if (objectiveModuleId is null)
        {
            return false;
        }

        var requiredDockModuleId = StarterStationLayoutResolver.ResolveDockModuleId(station.FactionId, world.ModuleDefinitions);
        if (!station.InstalledModules.Contains(requiredDockModuleId, StringComparer.Ordinal)
            || !station.InstalledModules.Contains(objectiveModuleId, StringComparer.Ordinal))
        {
            return false;
        }

        var powerModuleId = StarterStationLayoutResolver.ResolvePowerModuleId(station.FactionId, world.ModuleDefinitions);
        if (!string.Equals(objectiveModuleId, powerModuleId, StringComparison.Ordinal)
            && !station.InstalledModules.Contains(powerModuleId, StringComparer.Ordinal))
        {
            return false;
        }

        foreach (var storageModuleId in StarterStationLayoutResolver.ResolveRequiredStorageModuleIds(
          objectiveModuleId,
          station.FactionId,
          world.ModuleDefinitions,
          world.ItemDefinitions,
          world.Recipes))
        {
            if (!station.InstalledModules.Contains(storageModuleId, StringComparer.Ordinal))
            {
                return false;
            }
        }

        return true;
    }

    internal List<PolicySetRuntime> CreatePolicies(IReadOnlyCollection<FactionRuntime> factions)
    {
        var policies = new List<PolicySetRuntime>(factions.Count);
        foreach (var faction in factions)
        {
            var policyId = $"policy-{faction.Id}";
            faction.DefaultPolicySetId = policyId;
            policies.Add(new PolicySetRuntime
            {
                Id = policyId,
                OwnerKind = "faction",
                OwnerId = faction.Id,
            });
        }

        return policies;
    }

    internal List<CommanderRuntime> CreateCommanders(
      IReadOnlyCollection<FactionRuntime> factions,
      IReadOnlyCollection<StationRuntime> stations,
      IReadOnlyCollection<ShipRuntime> ships)
    {
        var commanders = new List<CommanderRuntime>();
        var factionCommanders = new Dictionary<string, CommanderRuntime>(StringComparer.Ordinal);
        var factionsById = factions.ToDictionary(faction => faction.Id, StringComparer.Ordinal);

        foreach (var faction in factions)
        {
            var commander = new CommanderRuntime
            {
                Id = $"commander-faction-{faction.Id}",
                Kind = CommanderKind.Faction,
                FactionId = faction.Id,
                ControlledEntityId = faction.Id,
                PolicySetId = faction.DefaultPolicySetId,
                Doctrine = "strategic-control",
            };

            commanders.Add(commander);
            factionCommanders[faction.Id] = commander;
            faction.CommanderIds.Add(commander.Id);
        }

        foreach (var station in stations)
        {
            if (!factionCommanders.TryGetValue(station.FactionId, out var parentCommander))
            {
                continue;
            }

            var commander = new CommanderRuntime
            {
                Id = $"commander-station-{station.Id}",
                Kind = CommanderKind.Station,
                FactionId = station.FactionId,
                ParentCommanderId = parentCommander.Id,
                ControlledEntityId = station.Id,
                PolicySetId = parentCommander.PolicySetId,
                Doctrine = "station-control",
            };

            station.CommanderId = commander.Id;
            station.PolicySetId = parentCommander.PolicySetId;
            parentCommander.SubordinateCommanderIds.Add(commander.Id);
            factionsById[station.FactionId].CommanderIds.Add(commander.Id);
            commanders.Add(commander);
        }

        foreach (var ship in ships)
        {
            if (!factionCommanders.TryGetValue(ship.FactionId, out var parentCommander))
            {
                continue;
            }

            var commander = new CommanderRuntime
            {
                Id = $"commander-ship-{ship.Id}",
                Kind = CommanderKind.Ship,
                FactionId = ship.FactionId,
                ParentCommanderId = parentCommander.Id,
                ControlledEntityId = ship.Id,
                PolicySetId = parentCommander.PolicySetId,
                Doctrine = "ship-control",
            };

            ship.CommanderId = commander.Id;
            ship.PolicySetId = parentCommander.PolicySetId;
            parentCommander.SubordinateCommanderIds.Add(commander.Id);
            factionsById[ship.FactionId].CommanderIds.Add(commander.Id);
            commanders.Add(commander);
        }

        return commanders;
    }

    internal PlayerFactionRuntime CreatePlayerFaction(
      IReadOnlyCollection<FactionRuntime> factions,
      IReadOnlyCollection<StationRuntime> stations,
      IReadOnlyCollection<ShipRuntime> ships,
      IReadOnlyCollection<CommanderRuntime> commanders,
      IReadOnlyCollection<PolicySetRuntime> policies,
      DateTimeOffset nowUtc)
    {
        var sovereignFaction = factions.OrderBy(faction => faction.Id, StringComparer.Ordinal).FirstOrDefault()
          ?? throw new InvalidOperationException("Cannot create a player faction without at least one faction in the world.");

        var player = new PlayerFactionRuntime
        {
            Id = "player-faction",
            Label = $"{sovereignFaction.Label} Command",
            SovereignFactionId = sovereignFaction.Id,
            CreatedAtUtc = nowUtc,
            UpdatedAtUtc = nowUtc,
        };

        foreach (var shipId in ships.Where(ship => ship.FactionId == sovereignFaction.Id).Select(ship => ship.Id))
        {
            player.AssetRegistry.ShipIds.Add(shipId);
        }

        foreach (var stationId in stations.Where(station => station.FactionId == sovereignFaction.Id).Select(station => station.Id))
        {
            player.AssetRegistry.StationIds.Add(stationId);
        }

        foreach (var commanderId in commanders.Where(commander => commander.FactionId == sovereignFaction.Id).Select(commander => commander.Id))
        {
            player.AssetRegistry.CommanderIds.Add(commanderId);
        }

        foreach (var policy in policies.Where(policy => string.Equals(policy.OwnerId, sovereignFaction.Id, StringComparison.Ordinal)))
        {
            player.AssetRegistry.PolicySetIds.Add(policy.Id);
        }

        player.Policies.Add(new PlayerFactionPolicyRuntime
        {
            Id = "player-core-policy",
            Label = "Core Empire Policy",
            ScopeKind = "player-faction",
            ScopeId = player.Id,
            PolicySetId = sovereignFaction.DefaultPolicySetId,
            TradeAccessPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.TradeAccessPolicy ?? "owner-and-allies",
            DockingAccessPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.DockingAccessPolicy ?? "owner-and-allies",
            ConstructionAccessPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.ConstructionAccessPolicy ?? "owner-only",
            OperationalRangePolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.OperationalRangePolicy ?? "unrestricted",
            CombatEngagementPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.CombatEngagementPolicy ?? "defensive",
            AvoidHostileSystems = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.AvoidHostileSystems ?? true,
            FleeHullRatio = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.FleeHullRatio ?? 0.35f,
            UpdatedAtUtc = nowUtc,
        });

        if (policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId) is { } defaultPolicy)
        {
            foreach (var systemId in defaultPolicy.BlacklistedSystemIds)
            {
                player.Policies[0].BlacklistedSystemIds.Add(systemId);
            }
        }

        player.AutomationPolicies.Add(new PlayerAutomationPolicyRuntime
        {
            Id = "player-core-automation",
            Label = "Core Automation",
            ScopeKind = "player-faction",
            ScopeId = player.Id,
            BehaviorKind = Idle,
            UpdatedAtUtc = nowUtc,
        });

        player.Reserves.Add(new PlayerReserveGroupRuntime
        {
            Id = "player-core-reserve",
            Label = "Strategic Reserve",
            ReserveKind = "military",
            UpdatedAtUtc = nowUtc,
        });
        player.AssetRegistry.ReserveIds.Add("player-core-reserve");

        return player;
    }

    internal FactionRuntime CreateFaction(string factionId)
    {
        if (!staticData.FactionDefinitions.TryGetValue(factionId, out var definition))
        {
            throw new InvalidOperationException($"Faction '{factionId}' is not defined in static data.");
        }

        return new FactionRuntime
        {
            Id = definition.Id,
            Label = definition.Label,
            Color = ResolveFactionColor(definition),
            Credits = MinimumFactionCredits,
        };
    }

    private static string ResolveFactionColor(FactionDefinition definition) =>
      definition.Id switch
      {
          "alliance" => "#c084fc",
          "antigone" => "#f97316",
          "argon" => "#3b82f6",
          "boron" => "#14b8a6",
          "freesplit" => "#ef4444",
          "hatikvah" => "#84cc16",
          "holyorder" => "#d97706",
          "loanshark" => "#f59e0b",
          "ministry" => "#a3e635",
          "paranid" => "#eab308",
          "pioneers" => "#60a5fa",
          "scaleplate" => "#94a3b8",
          "scavenger" => "#64748b",
          "split" => "#b91c1c",
          "teladi" => "#22c55e",
          "terran" => "#38bdf8",
          "trinity" => "#2dd4bf",
          "xenon" => "#9ca3af",
          _ => definition.RaceId switch
          {
              "argon" => "#3b82f6",
              "boron" => "#14b8a6",
              "paranid" => "#eab308",
              "split" => "#b91c1c",
              "teladi" => "#22c55e",
              "terran" => "#38bdf8",
              "xenon" => "#9ca3af",
              _ => "#94a3b8",
          },
      };

    private static void InitializeStationPopulation(
      StationRuntime station,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
    {
        station.PopulationCapacity = SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport.GetStationSupportedPopulation(moduleDefinitions, station);
        station.WorkforceRequired = SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport.GetStationRequiredWorkforce(moduleDefinitions, station);
        station.Population = station.PopulationCapacity > 40f
          ? MathF.Min(station.PopulationCapacity * 0.65f, station.WorkforceRequired * 1.05f)
          : MathF.Min(28f, station.PopulationCapacity);
        station.WorkforceEffectiveRatio = ComputeWorkforceRatio(station.Population, station.WorkforceRequired);
    }

}
