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

namespace SpaceGame.Api.Ships.AI;

public sealed partial class ShipAiService
{
    private ShipPlanRuntime BuildBehaviorFallbackPlan(SimulationWorld world, ShipRuntime ship)
    {
        var (behaviorKind, sourceId) = ResolveBehaviorSource(world, ship);
        var failureReason = ship.LastAccessFailureReason;
        if (string.Equals(behaviorKind, Idle, StringComparison.Ordinal))
        {
            return CreateIdlePlan(ship, AiPlanSourceKind.DefaultBehavior, sourceId, "Idle");
        }

        if (IsBehaviorBlockingFailure(behaviorKind, failureReason))
        {
            return CreateBlockedPlan(
                ship,
                AiPlanSourceKind.DefaultBehavior,
                sourceId,
                DescribeBehaviorFallbackSummary(world, ship, behaviorKind, failureReason),
                failureReason!);
        }

        return CreateIdlePlan(
            ship,
            AiPlanSourceKind.DefaultBehavior,
            sourceId,
            DescribeBehaviorFallbackSummary(world, ship, behaviorKind, failureReason));
    }

    private static bool IsBehaviorBlockingFailure(string behaviorKind, string? failureReason) => failureReason switch
    {
        "missing-item" => true,
        "no-suitable-buyer" => true,
        "no-mineable-node" when string.Equals(behaviorKind, LocalAutoMine, StringComparison.Ordinal) => true,
        _ => false,
    };

    private static string DescribeBehaviorFallbackSummary(SimulationWorld world, ShipRuntime ship, string behaviorKind, string? failureReason)
    {
        var assignment = ResolveAssignment(world, ship);
        var systemId = assignment?.TargetSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId;
        var itemId = assignment?.ItemId ?? ship.DefaultBehavior.ItemId ?? "resource";

        return failureReason switch
        {
            "missing-item" => "No mining ware configured",
            "no-suitable-buyer" => $"No buyer for {itemId} in {systemId}",
            "no-mineable-node" when string.Equals(behaviorKind, LocalAutoMine, StringComparison.Ordinal) => $"No {itemId} to mine in {systemId}",
            "no-mineable-node" => "No mineable node",
            "no-home-station" => "No home station",
            "no-trade-route" => "No trade route",
            "no-fleet-to-supply" => "No fleet to supply",
            "station-missing" => "No station to dock",
            "target-ship-missing" => "No ship to follow",
            "target-missing" => "No object target",
            "no-salvage-target" => "No salvage target",
            "no-repeat-orders" => "No repeat orders",
            "no-construction-site" => "No construction site",
            "support-station-missing" => "No support station",
            _ => "Idle",
        };
    }

    private ShipPlanRuntime BuildTradePlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, TradeRoutePlan route, string summary)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.TradeRoute,
          summary,
          [
            CreateStep("step-acquire", "acquire-cargo", $"Load {route.ItemId} at {route.SourceStation.Label}",
        [
          CreateSubTask("sub-acquire-travel", ShipTaskKinds.Travel, $"Travel to {route.SourceStation.Label}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, MathF.Max(route.SourceStation.Radius + 12f, 12f), 0f),
          CreateSubTask("sub-acquire-dock", ShipTaskKinds.Dock, $"Dock at {route.SourceStation.Label}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, 4f, 0f),
          CreateSubTask("sub-acquire-load", ShipTaskKinds.LoadCargo, $"Load {route.ItemId}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, 0f, ship.Definition.GetTotalCargoCapacity(), itemId: route.ItemId),
          CreateSubTask("sub-acquire-undock", ShipTaskKinds.Undock, $"Undock from {route.SourceStation.Label}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, MathF.Max(4f, 12f), 0f)
        ]),
        CreateStep("step-deliver", "deliver-cargo", $"Deliver {route.ItemId} to {route.DestinationStation.Label}",
        [
          CreateSubTask("sub-route-travel", ShipTaskKinds.Travel, $"Travel to {route.DestinationStation.Label}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, MathF.Max(route.DestinationStation.Radius + 12f, 12f), 0f),
          CreateSubTask("sub-route-dock", ShipTaskKinds.Dock, $"Dock at {route.DestinationStation.Label}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, 4f, 0f),
          CreateSubTask("sub-route-unload", ShipTaskKinds.UnloadCargo, $"Unload {route.ItemId}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, 0f, ship.Definition.GetTotalCargoCapacity(), itemId: route.ItemId),
          CreateSubTask("sub-route-undock", ShipTaskKinds.Undock, $"Undock from {route.DestinationStation.Label}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, MathF.Max(4f, 12f), 0f)
        ])
          ]);
    }

    private ShipPlanRuntime BuildFleetSupplyPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, FleetSupplyPlan plan)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          SupplyFleet,
          plan.Summary,
          [
            CreateStep("step-fleet-acquire", "acquire-cargo", $"Load {plan.ItemId} at {plan.SourceStation.Label}",
        [
          CreateSubTask("sub-fleet-source-travel", ShipTaskKinds.Travel, $"Travel to {plan.SourceStation.Label}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, MathF.Max(plan.SourceStation.Radius + 12f, 12f), 0f),
          CreateSubTask("sub-fleet-source-dock", ShipTaskKinds.Dock, $"Dock at {plan.SourceStation.Label}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, 4f, 0f),
          CreateSubTask("sub-fleet-source-load", ShipTaskKinds.LoadCargo, $"Load {plan.ItemId}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, 0f, plan.Amount, itemId: plan.ItemId),
          CreateSubTask("sub-fleet-source-undock", ShipTaskKinds.Undock, $"Undock from {plan.SourceStation.Label}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, 12f, 0f),
        ]),
        CreateStep("step-fleet-deliver", "deliver-fleet", $"Deliver {plan.ItemId} to {plan.TargetShip.Definition.Name}",
          [
          CreateSubTask("sub-fleet-follow", ShipTaskKinds.FollowTarget, $"Rendezvous with {plan.TargetShip.Definition.Name}", plan.TargetShip.SystemId, plan.TargetShip.Position, plan.TargetShip.Id, plan.Radius, 6f),
          CreateSubTask("sub-fleet-transfer", ShipTaskKinds.TransferCargoToShip, $"Transfer {plan.ItemId}", plan.TargetShip.SystemId, plan.TargetShip.Position, plan.TargetShip.Id, plan.Radius, plan.Amount, itemId: plan.ItemId),
        ])
          ]);
    }

    private ShipPlanRuntime BuildConstructionPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ConstructionSiteRuntime site, StationRuntime supportStation, string summary)
    {
        var targetPosition = site.StationId is null ? supportStation.Position : supportStation.Position;
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          "construction-support",
          summary,
          [
            CreateStep("step-construction-deliver", "deliver-materials", $"Deliver materials to {site.Id}",
        [
          CreateSubTask("sub-construction-travel", ShipTaskKinds.Travel, $"Travel to support station {supportStation.Label}", supportStation.SystemId, targetPosition, supportStation.Id, MathF.Max(supportStation.Radius + 18f, 18f), 0f),
          CreateSubTask("sub-construction-deliver", ShipTaskKinds.DeliverConstruction, $"Deliver materials to {site.Id}", site.SystemId, site.CelestialId is null ? supportStation.Position : supportStation.Position, site.Id, 12f, 0f)
        ]),
        CreateStep("step-construction-build", "build-site", $"Build {site.Id}",
        [
          CreateSubTask("sub-construction-build", ShipTaskKinds.BuildConstructionSite, $"Build {site.Id}", site.SystemId, site.CelestialId is null ? supportStation.Position : supportStation.Position, site.Id, 12f, 0f)
        ])
          ]);
    }

    private ShipPlanRuntime BuildAttackPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, string targetEntityId, string? targetSystemId, string summary)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.AttackTarget,
          summary,
          [
            CreateStep("step-attack", ShipOrderKinds.AttackTarget, summary,
        [
          CreateSubTask("sub-attack", ShipTaskKinds.AttackTarget, summary, targetSystemId ?? ship.SystemId, ship.Position, targetEntityId, 26f, 0f)
        ])
          ]);
    }

    private ShipPlanRuntime BuildDockAndWaitPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, StationRuntime station, float waitSeconds, string summary)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.DockAndWait,
          summary,
          [
            CreateStep("step-dock-wait-travel", "travel", $"Travel to {station.Label}",
        [
          CreateSubTask("sub-dock-wait-travel", ShipTaskKinds.Travel, $"Travel to {station.Label}", station.SystemId, station.Position, station.Id, MathF.Max(station.Radius + 12f, 12f), 0f),
          CreateSubTask("sub-dock-wait-dock", ShipTaskKinds.Dock, $"Dock at {station.Label}", station.SystemId, station.Position, station.Id, 4f, 0f),
          CreateSubTask("sub-dock-wait-hold", ShipTaskKinds.HoldPosition, $"Wait at {station.Label}", station.SystemId, station.Position, station.Id, 0f, waitSeconds),
        ])
          ]);
    }

    private ShipPlanRuntime BuildFlyAndWaitPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, string targetSystemId, Vector3 targetPosition, float waitSeconds, string summary)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.FlyAndWait,
          summary,
          [
            CreateStep("step-fly-wait", ShipOrderKinds.FlyAndWait, summary,
        [
          CreateSubTask("sub-fly-wait-travel", ShipTaskKinds.Travel, summary, targetSystemId, targetPosition, null, 6f, 0f),
          CreateSubTask("sub-fly-wait-hold", ShipTaskKinds.HoldPosition, summary, targetSystemId, targetPosition, null, 0f, waitSeconds),
        ])
          ]);
    }

    private ShipPlanRuntime BuildFlyToObjectPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, string targetSystemId, Vector3 targetPosition, string targetEntityId, string summary)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.FlyToObject,
          summary,
          [
            CreateStep("step-fly-object", ShipOrderKinds.FlyToObject, summary,
        [
          CreateSubTask("sub-fly-object-travel", ShipTaskKinds.Travel, summary, targetSystemId, targetPosition, targetEntityId, 8f, 0f),
          CreateSubTask("sub-fly-object-hold", ShipTaskKinds.HoldPosition, summary, targetSystemId, targetPosition, targetEntityId, 0f, MathF.Max(1f, ship.DefaultBehavior.WaitSeconds)),
        ])
          ]);
    }

    private ShipPlanRuntime BuildFollowShipPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, ShipRuntime targetShip, float radius, float durationSeconds, string summary)
    {
        return BuildFollowPlan(ship, sourceKind, sourceId, targetShip.Id, targetShip.SystemId, targetShip.Position, radius, durationSeconds, summary);
    }

    private ShipPlanRuntime BuildFollowPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, string targetEntityId, string targetSystemId, Vector3 targetPosition, float radius, float durationSeconds, string summary)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          ShipOrderKinds.FollowShip,
          summary,
          [
            CreateStep("step-follow", "follow-target", summary,
        [
          CreateSubTask("sub-follow", ShipTaskKinds.FollowTarget, summary, targetSystemId, targetPosition, targetEntityId, radius, durationSeconds),
        ])
          ]);
    }

    private ShipPlanRuntime CreateIdlePlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, string summary)
    {
        return CreatePlan(
          ship,
          sourceKind,
          sourceId,
          Idle,
          summary,
          [
            CreateStep("step-idle", ShipOrderKinds.HoldPosition, summary,
        [
          CreateSubTask("sub-idle", ShipTaskKinds.HoldPosition, summary, ship.SystemId, ship.Position, null, 0f, 1.5f)
        ])
          ]);
    }

    private ShipPlanRuntime CreateBlockedPlan(ShipRuntime ship, AiPlanSourceKind sourceKind, string sourceId, string summary, string blockingReason)
    {
        var subTask = CreateSubTask("sub-blocked", ShipTaskKinds.HoldPosition, summary, ship.SystemId, ship.Position, null, 0f, 0f);
        subTask.Status = WorkStatus.Blocked;
        subTask.BlockingReason = blockingReason;

        var step = CreateStep("step-blocked", "blocked", summary, [subTask]);
        step.Status = AiPlanStepStatus.Blocked;
        step.BlockingReason = blockingReason;

        var plan = CreatePlan(ship, sourceKind, sourceId, "blocked", summary, [step]);
        plan.Status = AiPlanStatus.Blocked;
        plan.FailureReason = blockingReason;
        return plan;
    }

    private static ShipPlanRuntime CreatePlan(
      ShipRuntime ship,
      AiPlanSourceKind sourceKind,
      string sourceId,
      string kind,
      string summary,
      IReadOnlyList<ShipPlanStepRuntime> steps)
    {
        var plan = new ShipPlanRuntime
        {
            Id = $"plan-{ship.Id}-{Guid.NewGuid():N}",
            SourceKind = sourceKind,
            SourceId = sourceId,
            Kind = kind,
            Summary = summary,
        };
        plan.Steps.AddRange(steps);
        return plan;
    }

    private static ShipPlanStepRuntime CreateStep(string id, string kind, string summary, IReadOnlyList<ShipSubTaskRuntime> subTasks)
    {
        var step = new ShipPlanStepRuntime
        {
            Id = id,
            Kind = kind,
            Summary = summary,
        };
        step.SubTasks.AddRange(subTasks);
        return step;
    }

    private static ShipSubTaskRuntime CreateSubTask(
      string id,
      string kind,
      string summary,
      string targetSystemId,
      Vector3 targetPosition,
      string? targetEntityId,
      float threshold,
      float amount,
      string? itemId = null,
      string? moduleId = null,
      string? targetNodeId = null) =>
      new()
      {
          Id = id,
          Kind = kind,
          Summary = summary,
          TargetSystemId = targetSystemId,
          TargetPosition = targetPosition,
          TargetEntityId = targetEntityId,
          TargetNodeId = targetNodeId,
          ItemId = itemId,
          ModuleId = moduleId,
          Threshold = threshold,
          Amount = amount,
      };
}
