using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
using static SpaceGame.Api.Stations.Simulation.InfrastructureSimulationService;
using static SpaceGame.Api.Stations.Simulation.StationSimulationService;

namespace SpaceGame.Api.Ships.AI;

public sealed partial class ShipAiService
{
    private ShipPlanRuntime? BuildEmergencyPlan(SimulationWorld world, ShipRuntime ship)
    {
        var policy = ResolvePolicy(world, ship.PolicySetId);
        if (policy is null)
        {
            return null;
        }

        var hullRatio = ship.Definition.Hull <= 0.01f ? 1f : ship.Health / ship.Definition.Hull;
        if (hullRatio > policy.FleeHullRatio)
        {
            return null;
        }

        var hostileNearby = world.Ships.Any(candidate =>
          candidate.Health > 0f &&
          candidate.FactionId != ship.FactionId &&
          candidate.SystemId == ship.SystemId &&
          candidate.Position.DistanceTo(ship.Position) <= 200f);
        if (!hostileNearby)
        {
            return null;
        }

        var safeStation = world.Stations
          .Where(station => station.FactionId == ship.FactionId)
          .OrderByDescending(station => station.SystemId == ship.SystemId ? 1 : 0)
          .ThenBy(station => station.Position.DistanceTo(ship.Position))
          .FirstOrDefault();

        var plan = new ShipPlanRuntime
        {
            Id = $"plan-{ship.Id}-safety-{Guid.NewGuid():N}",
            SourceKind = AiPlanSourceKind.Rule,
            SourceId = ShipOrderKinds.Flee,
            Kind = "safety-flee",
            Summary = "Emergency retreat",
        };

        if (safeStation is null)
        {
            plan.Steps.Add(CreateStep("step-flee-hold", ShipOrderKinds.HoldPosition, "Hold position away from hostiles",
            [
              CreateSubTask("sub-flee-hold", ShipTaskKinds.HoldPosition, "Hold safe position", ship.SystemId, ship.Position, null, 0f, 3f)
            ]));
            return plan;
        }

        plan.Steps.Add(CreateStep("step-flee-travel", "travel", "Travel to safe station",
        [
          CreateSubTask("sub-flee-travel", ShipTaskKinds.Travel, $"Travel to {safeStation.Label}", safeStation.SystemId, safeStation.Position, safeStation.Id, MathF.Max(balance.ArrivalThreshold, safeStation.Radius + 12f), 0f)
        ]));
        plan.Steps.Add(CreateStep("step-flee-dock", "dock", "Dock at safe station",
        [
          CreateSubTask("sub-flee-dock", ShipTaskKinds.Dock, $"Dock at {safeStation.Label}", safeStation.SystemId, safeStation.Position, safeStation.Id, 4f, 0f)
        ]));
        return plan;
    }

    private ShipPlanRuntime? BuildOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        return order.Kind switch
        {
            var kind when string.Equals(kind, ShipOrderKinds.Move, StringComparison.Ordinal) => BuildMovePlan(ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.DockAtStation, StringComparison.Ordinal) => BuildDockOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.DockAndWait, StringComparison.Ordinal) => BuildDockAndWaitOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.FlyAndWait, StringComparison.Ordinal) => BuildFlyAndWaitOrderPlan(ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.FlyToObject, StringComparison.Ordinal) => BuildFlyToObjectOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.FollowShip, StringComparison.Ordinal) => BuildFollowShipOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.TradeRoute, StringComparison.Ordinal) => BuildTradeOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.MineAndDeliver, StringComparison.Ordinal) => BuildMineOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.MineLocal, StringComparison.Ordinal) => BuildMineLocalOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.MineAndDeliverRun, StringComparison.Ordinal) => BuildMineAndDeliverRunOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.SellMinedCargo, StringComparison.Ordinal) => BuildSellMinedCargoOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.SupplyFleetRun, StringComparison.Ordinal) => BuildSupplyFleetOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.SalvageRun, StringComparison.Ordinal) => BuildAutoSalvageOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.BuildAtSite, StringComparison.Ordinal) => BuildBuildOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.AttackTarget, StringComparison.Ordinal) => BuildAttackOrderPlan(world, ship, order),
            var kind when string.Equals(kind, ShipOrderKinds.HoldPosition, StringComparison.Ordinal) => BuildHoldOrderPlan(ship, order),
            _ => null,
        };
    }

    private static (string BehaviorKind, string SourceId) ResolveBehaviorSource(SimulationWorld world, ShipRuntime ship)
    {
        var assignment = ResolveAssignment(world, ship);
        return assignment is null
          ? (ship.DefaultBehavior.Kind, ship.DefaultBehavior.Kind)
          : (assignment.BehaviorKind, assignment.ObjectiveId);
    }

    private ShipPlanRuntime BuildMovePlan(ShipRuntime ship, ShipOrderRuntime order)
    {
        var targetSystemId = order.TargetSystemId ?? ship.SystemId;
        var targetPosition = order.TargetPosition ?? ship.Position;
        return CreatePlan(
          ship,
          AiPlanSourceKind.Order,
          order.Id,
          ShipOrderKinds.Move,
          order.Label ?? "Move order",
          [
            CreateStep("step-move", "travel", order.Label ?? "Travel",
        [
          CreateSubTask("sub-move-travel", ShipTaskKinds.Travel, order.Label ?? "Travel", targetSystemId, targetPosition, order.TargetEntityId, 0f, 0f)
        ])
          ]);
    }

    private ShipPlanRuntime? BuildDockOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var station = ResolveStation(world, order.TargetEntityId ?? order.DestinationStationId);
        if (station is null)
        {
            order.FailureReason = "station-missing";
            return null;
        }

        return CreatePlan(
          ship,
          AiPlanSourceKind.Order,
          order.Id,
          "dock-at-station",
          order.Label ?? $"Dock at {station.Label}",
          [
            CreateStep("step-dock-travel", "travel", $"Travel to {station.Label}",
        [
          CreateSubTask("sub-dock-travel", ShipTaskKinds.Travel, $"Travel to {station.Label}", station.SystemId, station.Position, station.Id, MathF.Max(balance.ArrivalThreshold, station.Radius + 12f), 0f)
        ]),
        CreateStep("step-dock", "dock", $"Dock at {station.Label}",
        [
          CreateSubTask("sub-dock", ShipTaskKinds.Dock, $"Dock at {station.Label}", station.SystemId, station.Position, station.Id, 4f, 0f)
        ])
          ]);
    }

    private ShipPlanRuntime? BuildTradeOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        if (order.SourceStationId is null || order.DestinationStationId is null || order.ItemId is null)
        {
            order.FailureReason = "trade-order-incomplete";
            return null;
        }

        var route = ResolveTradeRoute(world, order.ItemId, order.SourceStationId, order.DestinationStationId);
        if (route is null)
        {
            order.FailureReason = "trade-route-missing";
            return null;
        }

        return BuildTradePlan(ship, AiPlanSourceKind.Order, order.Id, route, order.Label ?? route.Summary);
    }

    private ShipPlanRuntime? BuildMineOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var systemId = order.TargetSystemId ?? ship.SystemId;
        var itemId = order.ItemId;
        if (string.IsNullOrWhiteSpace(itemId))
        {
            order.FailureReason = "mine-order-item-missing";
            return null;
        }

        var node = ResolveNode(world, order.NodeId);
        if (node is not null)
        {
            if (!string.Equals(node.SystemId, systemId, StringComparison.Ordinal))
            {
                order.FailureReason = "mine-order-node-system-mismatch";
                return null;
            }

            if (!string.Equals(node.ItemId, itemId, StringComparison.Ordinal))
            {
                order.FailureReason = "mine-order-node-item-mismatch";
                return null;
            }
        }
        else
        {
            node = SelectLocalMiningNode(world, ship, systemId, itemId);
        }

        if (node is null)
        {
            order.FailureReason = "mine-order-node-missing";
            return null;
        }

        return BuildLocalMiningPlan(ship, AiPlanSourceKind.Order, order.Id, node, order.Label ?? $"Mine {itemId} in {systemId}");
    }

    private ShipPlanRuntime? BuildMineLocalOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var node = ResolveNode(world, order.NodeId);
        if (node is null)
        {
            order.FailureReason = "mine-order-incomplete";
            return null;
        }

        return BuildLocalMiningPlan(ship, AiPlanSourceKind.Order, order.Id, node, order.Label ?? $"Mine {node.ItemId}");
    }

    private ShipPlanRuntime? BuildMineAndDeliverRunOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var node = ResolveNode(world, order.NodeId);
        var buyer = ResolveStation(world, order.DestinationStationId);
        if (node is null || buyer is null)
        {
            order.FailureReason = "mine-and-deliver-order-incomplete";
            return null;
        }

        return BuildMiningPlan(ship, AiPlanSourceKind.Order, order.Id, node, buyer, order.Label ?? $"Mine {node.ItemId} for {buyer.Label}");
    }

    private ShipPlanRuntime? BuildSellMinedCargoOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var buyer = ResolveStation(world, order.DestinationStationId ?? order.TargetEntityId);
        if (buyer is null || string.IsNullOrWhiteSpace(order.ItemId))
        {
            order.FailureReason = "sell-order-incomplete";
            return null;
        }

        return BuildLocalMiningDeliveryPlan(ship, AiPlanSourceKind.Order, order.Id, buyer, order.ItemId, order.Label ?? $"Sell {order.ItemId}");
    }

    private ShipPlanRuntime? BuildAutoSalvageOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var homeStation = ResolveStation(world, order.SourceStationId ?? ship.DefaultBehavior.HomeStationId);
        var wreck = world.Wrecks.FirstOrDefault(candidate => candidate.Id == order.TargetEntityId && candidate.RemainingAmount > 0.01f);
        if (homeStation is null || wreck is null)
        {
            order.FailureReason = "salvage-order-incomplete";
            return null;
        }

        var approach = GetFormationPosition(wreck.Position, ship.Id, MathF.Max(8f, order.Radius > 0f ? order.Radius : ship.DefaultBehavior.Radius * 0.25f));
        return CreatePlan(
          ship,
          AiPlanSourceKind.Order,
          order.Id,
          AutoSalvage,
          order.Label ?? $"Salvage {wreck.ItemId}",
          [
            CreateStep("step-salvage-collect", "salvage", $"Salvage {wreck.ItemId}",
        [
          CreateSubTask("sub-salvage-travel", ShipTaskKinds.Travel, $"Travel to wreck {wreck.Id}", wreck.SystemId, approach, wreck.Id, 8f, 0f),
          CreateSubTask("sub-salvage-work", ShipTaskKinds.SalvageWreck, $"Salvage {wreck.ItemId}", wreck.SystemId, approach, wreck.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: wreck.ItemId),
        ]),
        CreateStep("step-salvage-deliver", "deliver-salvage", $"Deliver salvage to {homeStation.Label}",
        [
          CreateSubTask("sub-salvage-deliver-travel", ShipTaskKinds.Travel, $"Travel to {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, MathF.Max(homeStation.Radius + 12f, 12f), 0f),
          CreateSubTask("sub-salvage-deliver-dock", ShipTaskKinds.Dock, $"Dock at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 4f, 0f),
          CreateSubTask("sub-salvage-deliver-unload", ShipTaskKinds.UnloadCargo, $"Unload salvage at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 0f, ship.Definition.GetTotalCargoCapacity(), itemId: wreck.ItemId),
          CreateSubTask("sub-salvage-deliver-undock", ShipTaskKinds.Undock, $"Undock from {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 12f, 0f),
        ])
          ]);
    }

    private ShipPlanRuntime? BuildSupplyFleetOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var sourceStation = ResolveStation(world, order.SourceStationId);
        var targetShip = world.Ships.FirstOrDefault(candidate => candidate.Id == order.TargetEntityId && candidate.Health > 0f);
        if (sourceStation is null || targetShip is null || string.IsNullOrWhiteSpace(order.ItemId))
        {
            order.FailureReason = "supply-fleet-order-incomplete";
            return null;
        }

        var amount = MathF.Min(
            MathF.Max(10f, ship.Definition.GetTotalCargoCapacity() * 0.5f),
            GetInventoryAmount(sourceStation.Inventory, order.ItemId));
        if (amount <= 0.01f)
        {
            order.FailureReason = "supply-item-unavailable";
            return null;
        }

        var plan = new FleetSupplyPlan(
            sourceStation,
            targetShip,
            order.ItemId,
            amount,
            MathF.Max(16f, order.Radius),
            order.Label ?? $"Supply {targetShip.Definition.Name} with {order.ItemId}");
        return BuildFleetSupplyPlan(ship, AiPlanSourceKind.Order, order.Id, plan);
    }

    private ShipPlanRuntime? BuildBuildOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == (order.ConstructionSiteId ?? order.TargetEntityId));
        if (site is null)
        {
            order.FailureReason = "construction-site-missing";
            return null;
        }

        var supportStation = ResolveSupportStation(world, ship, site);
        if (supportStation is null)
        {
            order.FailureReason = "support-station-missing";
            return null;
        }

        return BuildConstructionPlan(ship, AiPlanSourceKind.Order, order.Id, site, supportStation, order.Label ?? $"Build {site.BlueprintId}");
    }

    private ShipPlanRuntime? BuildAttackOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var targetId = order.TargetEntityId;
        if (targetId is null)
        {
            order.FailureReason = "attack-target-missing";
            return null;
        }

        return BuildAttackPlan(ship, AiPlanSourceKind.Order, order.Id, targetId, order.TargetSystemId, order.Label ?? "Attack target");
    }

    private ShipPlanRuntime BuildHoldOrderPlan(ShipRuntime ship, ShipOrderRuntime order)
    {
        return CreatePlan(
          ship,
          AiPlanSourceKind.Order,
          order.Id,
          ShipOrderKinds.HoldPosition,
          order.Label ?? "Hold position",
          [
            CreateStep("step-hold", ShipOrderKinds.HoldPosition, order.Label ?? "Hold position",
        [
          CreateSubTask("sub-hold", ShipTaskKinds.HoldPosition, order.Label ?? "Hold position", order.TargetSystemId ?? ship.SystemId, order.TargetPosition ?? ship.Position, order.TargetEntityId, 0f, 3f)
        ])
          ]);
    }

    private ShipPlanRuntime? BuildDockAndWaitOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var station = ResolveStation(world, order.TargetEntityId ?? order.DestinationStationId ?? ship.DefaultBehavior.HomeStationId);
        if (station is null)
        {
            order.FailureReason = "station-missing";
            return null;
        }

        return BuildDockAndWaitPlan(ship, AiPlanSourceKind.Order, order.Id, station, MathF.Max(1f, order.WaitSeconds), order.Label ?? $"Dock and wait at {station.Label}");
    }

    private ShipPlanRuntime BuildFlyAndWaitOrderPlan(ShipRuntime ship, ShipOrderRuntime order)
    {
        var systemId = order.TargetSystemId ?? ship.SystemId;
        var targetPosition = order.TargetPosition ?? ship.Position;
        return BuildFlyAndWaitPlan(ship, AiPlanSourceKind.Order, order.Id, systemId, targetPosition, MathF.Max(1f, order.WaitSeconds), order.Label ?? "Fly and wait");
    }

    private ShipPlanRuntime? BuildFlyToObjectOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var targetEntityId = order.TargetEntityId;
        if (targetEntityId is null)
        {
            order.FailureReason = "target-missing";
            return null;
        }

        var objectTarget = ResolveObjectTarget(world, targetEntityId);
        if (objectTarget is null)
        {
            order.FailureReason = "target-missing";
            return null;
        }

        return BuildFlyToObjectPlan(ship, AiPlanSourceKind.Order, order.Id, objectTarget.Value.SystemId, objectTarget.Value.Position, targetEntityId, order.Label ?? $"Fly to {targetEntityId}");
    }

    private ShipPlanRuntime? BuildFollowShipOrderPlan(SimulationWorld world, ShipRuntime ship, ShipOrderRuntime order)
    {
        var targetShip = world.Ships.FirstOrDefault(candidate => candidate.Id == order.TargetEntityId && candidate.Health > 0f);
        if (targetShip is null)
        {
            order.FailureReason = "target-ship-missing";
            return null;
        }

        return BuildFollowShipPlan(ship, AiPlanSourceKind.Order, order.Id, targetShip, MathF.Max(order.Radius, 18f), MathF.Max(2f, order.WaitSeconds), order.Label ?? $"Follow {targetShip.Definition.Name}");
    }

    private ShipPlanRuntime BuildMiningPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ResourceNodeRuntime node, StationRuntime homeStation, string summary)
    {
        var extractionPosition = GetResourceHoldPosition(node.Position, ship.Id, 20f);
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.MineAndDeliver,
          summary,
          [
            CreateStep("step-mine", "mine", $"Mine {node.ItemId}",
        [
          CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} node", node.SystemId, extractionPosition, node.Id, 8f, 0f),
          CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity())
        ]),
        CreateStep("step-deliver", "deliver", $"Deliver {node.ItemId} to {homeStation.Label}",
        [
          CreateSubTask("sub-deliver-travel", ShipTaskKinds.Travel, $"Travel to {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, MathF.Max(homeStation.Radius + 12f, 12f), 0f),
          CreateSubTask("sub-deliver-dock", ShipTaskKinds.Dock, $"Dock at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 4f, 0f),
          CreateSubTask("sub-deliver-unload", ShipTaskKinds.UnloadCargo, $"Unload at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 0f, ship.Definition.GetTotalCargoCapacity()),
          CreateSubTask("sub-deliver-undock", ShipTaskKinds.Undock, $"Undock from {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, MathF.Max(4f, 12f), 0f)
        ])
          ]);
    }

    private ShipPlanRuntime BuildLocalMiningPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ResourceNodeRuntime node, string summary)
    {
        var extractionPosition = GetResourceHoldPosition(node.Position, ship.Id, 20f);
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.MineLocal,
          summary,
          [
            CreateStep("step-mine", "mine", $"Mine {node.ItemId}",
        [
          CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} node", node.SystemId, extractionPosition, node.Id, 8f, 0f),
          CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: node.ItemId)
        ])
          ]);
    }

    private ShipPlanRuntime BuildLocalMiningDeliveryPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, StationRuntime buyer, string itemId, string summary)
    {
        var amount = MathF.Max(1f, GetInventoryAmount(ship.Inventory, itemId));
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.SellMinedCargo,
          summary,
          [
            CreateStep("step-deliver", "deliver", $"Deliver {itemId} to {buyer.Label}",
        [
          CreateSubTask("sub-deliver-travel", ShipTaskKinds.Travel, $"Travel to {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, MathF.Max(buyer.Radius + 12f, 12f), 0f),
          CreateSubTask("sub-deliver-dock", ShipTaskKinds.Dock, $"Dock at {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, 4f, 0f),
          CreateSubTask("sub-deliver-unload", ShipTaskKinds.UnloadCargo, $"Unload {itemId} at {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, 0f, amount, itemId: itemId),
          CreateSubTask("sub-deliver-undock", ShipTaskKinds.Undock, $"Undock from {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, MathF.Max(4f, 12f), 0f)
        ])
          ]);
    }
}
