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

namespace SpaceGame.Api.Universe.Scenario;

public sealed class ScenarioContentBuilder(
    IStaticDataProvider staticData,
    IBalanceService balance)
{
    public ScenarioWorldContent Build(
      ScenarioDefinition scenario,
      WorldBuildTopology topology)
    {
        var stations = CreateStations(
          scenario,
          topology.SystemsById,
          topology.SpatialLayout.SystemGraphs,
          topology.SpatialLayout.Celestials);

        var patrolRoutes = BuildPatrolRoutes(scenario, topology.SystemsById);
        var ships = CreateShips(
          scenario,
          topology.SystemsById,
          topology.SpatialLayout.Celestials,
          patrolRoutes,
          stations);

        return new ScenarioWorldContent(stations, ships);
    }

    private List<StationRuntime> CreateStations(
      ScenarioDefinition scenario,
      IReadOnlyDictionary<string, SystemRuntime> systemsById,
      IReadOnlyDictionary<string, SystemSpatialGraph> systemGraphs,
      IReadOnlyCollection<CelestialRuntime> celestials)
    {
        var stations = new List<StationRuntime>();
        var stationIdCounter = 0;

        foreach (var plan in scenario.InitialStations)
        {
            if (!systemsById.TryGetValue(plan.SystemId, out var system))
            {
                throw new InvalidOperationException($"Scenario station '{plan.Label}' references unknown system '{plan.SystemId}'.");
            }

            var placement = SpatialBuilder.ResolveStationPlacement(plan, system, systemGraphs[system.Definition.Id], celestials);
            var station = new StationRuntime
            {
                Id = $"station-{++stationIdCounter}",
                SystemId = system.Definition.Id,
                Label = plan.Label,
                Color = plan.Color,
                Objective = StationSimulationService.NormalizeStationObjective(plan.Objective),
                Position = placement.Position,
                FactionId = GetRequiredFactionId(plan.FactionId, $"station '{plan.Label}'"),
                CelestialId = placement.AnchorCelestial.Id,
                Health = 600f,
                MaxHealth = 600f,
            };

            stations.Add(station);
            placement.AnchorCelestial.OccupyingStructureId = station.Id;

            var startingModules = BuildStartingModules(plan);
            foreach (var moduleId in startingModules)
            {
                AddStationModule(station, staticData.ModuleDefinitions, moduleId);
            }
        }

        return stations;
    }

    private IReadOnlyList<string> BuildStartingModules(InitialStationDefinition plan)
    {
        var startingModules = new List<string>(plan.StartingModules.Count > 0
          ? plan.StartingModules
          : []);

        EnsureStartingModule(startingModules, StarterStationLayoutResolver.ResolveDockModuleId(plan.FactionId, staticData.ModuleDefinitions));

        var powerModuleId = StarterStationLayoutResolver.ResolvePowerModuleId(plan.FactionId, staticData.ModuleDefinitions);
        EnsureStartingModule(startingModules, powerModuleId);

        var defaultContainerStorageModuleId = StarterStationLayoutResolver.ResolveRequiredStorageModuleIds(
          powerModuleId,
          plan.FactionId,
          staticData.ModuleDefinitions,
          staticData.ItemDefinitions,
          staticData.Recipes)
          .FirstOrDefault(moduleId =>
          {
              return staticData.ModuleDefinitions.TryGetValue(moduleId, out var definition)
                && definition is StorageModuleDefinition storageDefinition
                && storageDefinition.StorageKind == StorageKind.Container;
          });

        if (defaultContainerStorageModuleId is not null)
        {
            EnsureStartingModule(startingModules, defaultContainerStorageModuleId);
        }

        var objectiveModuleId = StarterStationLayoutResolver.ResolveObjectiveModuleId(plan.Objective, plan.FactionId, staticData.ModuleDefinitions);
        if (!string.IsNullOrWhiteSpace(objectiveModuleId))
        {
            EnsureStartingModule(startingModules, objectiveModuleId);

            if (!string.Equals(objectiveModuleId, powerModuleId, StringComparison.Ordinal))
            {
                EnsureStartingModule(startingModules, powerModuleId);
            }

            foreach (var storageModuleId in StarterStationLayoutResolver.ResolveRequiredStorageModuleIds(
              objectiveModuleId,
              plan.FactionId,
              staticData.ModuleDefinitions,
              staticData.ItemDefinitions,
              staticData.Recipes))
            {
                EnsureStartingModule(startingModules, storageModuleId);
            }
        }

        foreach (var moduleId in startingModules)
        {
            if (!staticData.ModuleDefinitions.ContainsKey(moduleId))
            {
                throw new InvalidOperationException($"Station '{plan.Label}' requires module '{moduleId}', but it is not defined in static data.");
            }
        }

        return startingModules;
    }

    private static void EnsureStartingModule(List<string> modules, string moduleId)
    {
        if (!modules.Contains(moduleId, StringComparer.Ordinal))
        {
            modules.Add(moduleId);
        }
    }

    private static Dictionary<string, List<Vector3>> BuildPatrolRoutes(
      ScenarioDefinition scenario,
      IReadOnlyDictionary<string, SystemRuntime> systemsById)
    {
        return scenario.PatrolRoutes
          .GroupBy(route => route.SystemId, StringComparer.Ordinal)
          .ToDictionary(
            group => group.Key,
            group => group
              .SelectMany(route => route.Points)
              .Select(point => NormalizeScenarioPoint(systemsById[group.Key], point))
              .ToList(),
            StringComparer.Ordinal);
    }

    private List<ShipRuntime> CreateShips(
      ScenarioDefinition scenario,
      IReadOnlyDictionary<string, SystemRuntime> systemsById,
      IReadOnlyCollection<CelestialRuntime> celestials,
      IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
      IReadOnlyCollection<StationRuntime> stations)
    {
        var ships = new List<ShipRuntime>();
        var shipIdCounter = 0;

        foreach (var formation in scenario.ShipFormations)
        {
            if (!staticData.ShipDefinitions.TryGetValue(formation.ShipId, out var definition))
            {
                throw new InvalidOperationException($"Scenario ship formation references unknown ship '{formation.ShipId}'.");
            }

            for (var index = 0; index < formation.Count; index += 1)
            {
                var offset = new Vector3((index % 3) * 18f, balance.YPlane, (index / 3) * 18f);
                var position = Add(NormalizeScenarioPoint(systemsById[formation.SystemId], formation.Center), offset);
                var factionId = GetRequiredFactionId(formation.FactionId, $"ship formation '{formation.ShipId}' in system '{formation.SystemId}'");

                ships.Add(new ShipRuntime
                {
                    Id = $"ship-{++shipIdCounter}",
                    SystemId = formation.SystemId,
                    Definition = definition,
                    FactionId = factionId,
                    Position = position,
                    TargetPosition = position,
                    SpatialState = SpatialBuilder.CreateInitialShipSpatialState(formation.SystemId, position, celestials),
                    DefaultBehavior = CreateBehavior(
                      definition,
                      formation.SystemId,
                      factionId,
                      patrolRoutes,
                      stations),
                    Skills = ShipBootstrapPolicy.CreateSkills(definition),
                    Health = definition.Hull,
                });

                foreach (var (itemId, amount) in formation.StartingInventory)
                {
                    if (amount > 0f)
                    {
                        ships[^1].Inventory[itemId] = amount;
                    }
                }
            }
        }

        return ships;
    }

    private static string GetRequiredFactionId(string? factionId, string context)
    {
        if (!string.IsNullOrWhiteSpace(factionId))
        {
            return factionId;
        }

        throw new InvalidOperationException($"Scenario {context} is missing a factionId.");
    }

    private static DefaultBehaviorRuntime CreateBehavior(
      ShipDefinition definition,
      string systemId,
      string factionId,
      IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
      IReadOnlyCollection<StationRuntime> stations)
    {
        var homeStation = stations.FirstOrDefault(station =>
            string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
            && string.Equals(station.SystemId, systemId, StringComparison.Ordinal))
          ?? stations.FirstOrDefault(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal));

        if (IsConstructionShip(definition) && homeStation is not null)
        {
            return new DefaultBehaviorRuntime
            {
                Kind = ConstructStation,
                HomeSystemId = homeStation.SystemId,
                HomeStationId = homeStation.Id,
                PreferredConstructionSiteId = null,
            };
        }

        if (IsMiningShip(definition) && homeStation is not null)
        {
            return new DefaultBehaviorRuntime
            {
                Kind = definition.GetTotalCargoCapacity() >= 120f ? ExpertAutoMine : AdvancedAutoMine,
                HomeSystemId = homeStation.SystemId,
                HomeStationId = homeStation.Id,
                AreaSystemId = homeStation.SystemId,
                MaxSystemRange = definition.GetTotalCargoCapacity() >= 120f ? 3 : 1,
            };
        }

        if (IsTransportShip(definition))
        {
            return new DefaultBehaviorRuntime
            {
                Kind = AdvancedAutoTrade,
                HomeSystemId = homeStation?.SystemId ?? systemId,
                HomeStationId = homeStation?.Id,
                MaxSystemRange = 2,
            };
        }

        if (IsMilitaryShip(definition) && patrolRoutes.TryGetValue(systemId, out var route))
        {
            return new DefaultBehaviorRuntime
            {
                Kind = Patrol,
                HomeSystemId = homeStation?.SystemId ?? systemId,
                HomeStationId = homeStation?.Id,
                AreaSystemId = systemId,
                PatrolPoints = route,
                PatrolIndex = 0,
            };
        }

        return new DefaultBehaviorRuntime
        {
            Kind = HoldPosition,
            HomeSystemId = homeStation?.SystemId ?? systemId,
            HomeStationId = homeStation?.Id,
            AreaSystemId = homeStation?.SystemId ?? systemId,
        };
    }
}
