using SpaceGame.Api.Shared.Runtime;
using SpaceGame.Api.Ships.AI;
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;

namespace SpaceGame.Api.Stations.Simulation;

internal sealed class StationLifecycleService
{
    private const float WaterConsumptionPerWorkerPerSecond = 0.004f;
    private const float PopulationGrowthPerSecond = 0.012f;
    private const float PopulationAttritionPerSecond = 0.018f;
    private readonly StationSimulationService _stationSimulation;

    internal StationLifecycleService(StationSimulationService stationSimulation)
    {
        _stationSimulation = stationSimulation;
    }

    internal void UpdateStations(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
    {
        var factionPopulation = new Dictionary<string, float>(StringComparer.Ordinal);
        foreach (var station in world.Stations)
        {
            UpdateStationPopulation(world, station, deltaSeconds, events);
            _stationSimulation.ReviewStationMarketOrders(world, station);
            _stationSimulation.RunStationProduction(world, station, deltaSeconds, events);
            factionPopulation[station.FactionId] = GetInventoryAmount(factionPopulation, station.FactionId) + station.Population;
        }

        foreach (var faction in world.Factions)
        {
            faction.PopulationTotal = GetInventoryAmount(factionPopulation, faction.Id);
        }
    }

    private void UpdateStationPopulation(SimulationWorld world, StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
    {
        station.WorkforceRequired = GetStationRequiredWorkforce(world.ModuleDefinitions, station);

        var requiredWater = station.Population * WaterConsumptionPerWorkerPerSecond * deltaSeconds;
        var consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
        var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
        station.PopulationCapacity = GetStationSupportedPopulation(world.ModuleDefinitions, station);

        if (waterSatisfied)
        {
            if (station.PopulationCapacity > 40f && station.Population < station.PopulationCapacity)
            {
                station.Population = MathF.Min(station.PopulationCapacity, station.Population + (PopulationGrowthPerSecond * deltaSeconds));
            }
        }
        else if (station.Population > 0f)
        {
            var previous = station.Population;
            station.Population = MathF.Max(0f, station.Population - (PopulationAttritionPerSecond * deltaSeconds));
            if (MathF.Floor(previous) > MathF.Floor(station.Population))
            {
                events.Add(new SimulationEventRecord("station", station.Id, "population-loss", $"{station.Label} lost population due to support shortages.", DateTimeOffset.UtcNow));
            }
        }

        station.WorkforceEffectiveRatio = ComputeWorkforceRatio(station.Population, station.WorkforceRequired);
    }

    internal static float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
    {
        if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
        {
            return 0f;
        }

        var spawnPosition = new Vector3(station.Position.X + GetStationRadius(world, station) + 32f, station.Position.Y, station.Position.Z);
        var ship = new ShipRuntime
        {
            Id = $"ship-{world.Ships.Count + 1}",
            SystemId = station.SystemId,
            Definition = definition,
            FactionId = station.FactionId,
            Position = spawnPosition,
            TargetPosition = spawnPosition,
            SpatialState = CreateSpawnedShipSpatialState(station, spawnPosition),
            DefaultBehavior = CreateSpawnedShipBehavior(definition, station),
            Skills = ShipBootstrapPolicy.CreateSkills(definition),
            Health = definition.Hull,
        };

        world.Ships.Add(ship);
        EnsureSpawnedShipCommander(world, station, ship);
        if (world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId) is { } faction)
        {
            faction.ShipsBuilt += 1;
        }

        events.Add(new SimulationEventRecord("station", station.Id, "ship-built", $"{station.Label} launched {definition.Name}.", DateTimeOffset.UtcNow));
        return 1f;
    }

    private static ShipSpatialStateRuntime CreateSpawnedShipSpatialState(StationRuntime station, Vector3 position) => new()
    {
        CurrentSystemId = station.SystemId,
        SpaceLayer = SpaceLayerKind.LocalSpace,
        CurrentCelestialId = station.CelestialId,
        LocalPosition = position,
        SystemPosition = position,
        MovementRegime = MovementRegimeKind.LocalFlight,
    };

    private static DefaultBehaviorRuntime CreateSpawnedShipBehavior(ShipDefinition definition, StationRuntime station)
    {
        if (!IsMilitaryShip(definition))
        {
            return new DefaultBehaviorRuntime
            {
                Kind = IsTransportShip(definition) ? AdvancedAutoTrade : HoldPosition,
                HomeSystemId = station.SystemId,
                HomeStationId = station.Id,
                AreaSystemId = station.SystemId,
                MaxSystemRange = IsTransportShip(definition) ? 2 : 0,
            };
        }

        var patrolRadius = station.Radius + 90f;
        return new DefaultBehaviorRuntime
        {
            Kind = Patrol,
            HomeSystemId = station.SystemId,
            HomeStationId = station.Id,
            AreaSystemId = station.SystemId,
            PatrolPoints =
          [
            new Vector3(station.Position.X + patrolRadius, station.Position.Y, station.Position.Z),
        new Vector3(station.Position.X, station.Position.Y, station.Position.Z + patrolRadius),
        new Vector3(station.Position.X - patrolRadius, station.Position.Y, station.Position.Z),
        new Vector3(station.Position.X, station.Position.Y, station.Position.Z - patrolRadius),
      ],
        };
    }

    internal static void EnsureStationCommander(SimulationWorld world, StationRuntime station)
    {
        if (!string.IsNullOrWhiteSpace(station.CommanderId))
        {
            return;
        }

        var factionCommander = world.Commanders.FirstOrDefault(candidate =>
            string.Equals(candidate.Kind, CommanderKind.Faction, StringComparison.Ordinal)
            && string.Equals(candidate.FactionId, station.FactionId, StringComparison.Ordinal));
        var faction = world.Factions.FirstOrDefault(candidate => string.Equals(candidate.Id, station.FactionId, StringComparison.Ordinal));
        if (factionCommander is null || faction is null)
        {
            return;
        }

        var commander = new CommanderRuntime
        {
            Id = $"commander-station-{station.Id}",
            Kind = CommanderKind.Station,
            FactionId = station.FactionId,
            ParentCommanderId = factionCommander.Id,
            ControlledEntityId = station.Id,
            PolicySetId = factionCommander.PolicySetId,
            Doctrine = "station-control",
            Skills = new CommanderSkillProfileRuntime
            {
                Leadership = 3,
                Coordination = Math.Clamp(3 + (station.Modules.Count / 8), 3, 5),
                Strategy = 3,
            },
        };

        station.CommanderId = commander.Id;
        station.PolicySetId = factionCommander.PolicySetId;
        factionCommander.SubordinateCommanderIds.Add(commander.Id);
        faction.CommanderIds.Add(commander.Id);
        world.Commanders.Add(commander);
    }

    private static void EnsureSpawnedShipCommander(SimulationWorld world, StationRuntime station, ShipRuntime ship)
    {
        var factionCommander = world.Commanders.FirstOrDefault(candidate =>
            string.Equals(candidate.Kind, CommanderKind.Faction, StringComparison.Ordinal)
            && string.Equals(candidate.FactionId, station.FactionId, StringComparison.Ordinal));
        var faction = world.Factions.FirstOrDefault(candidate => string.Equals(candidate.Id, station.FactionId, StringComparison.Ordinal));
        if (factionCommander is null || faction is null)
        {
            return;
        }

        var commander = new CommanderRuntime
        {
            Id = $"commander-ship-{ship.Id}",
            Kind = CommanderKind.Ship,
            FactionId = ship.FactionId,
            ParentCommanderId = factionCommander.Id,
            ControlledEntityId = ship.Id,
            PolicySetId = factionCommander.PolicySetId,
            Doctrine = "ship-control",
            Skills = new CommanderSkillProfileRuntime
            {
                Leadership = Math.Clamp((ship.Skills.Navigation + ship.Skills.Combat + 1) / 2, 2, 5),
                Coordination = Math.Clamp((ship.Skills.Trade + ship.Skills.Mining + 1) / 2, 2, 5),
                Strategy = Math.Clamp((ship.Skills.Combat + ship.Skills.Construction + 1) / 2, 2, 5),
            },
        };

        ship.CommanderId = commander.Id;
        ship.PolicySetId = factionCommander.PolicySetId;
        factionCommander.SubordinateCommanderIds.Add(commander.Id);
        faction.CommanderIds.Add(commander.Id);
        world.Commanders.Add(commander);
    }
}
