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 const float WarpEngageDistanceKilometers = 250_000f;
    private const float FrigateDps = 7f;
    private const float DestroyerDps = 12f;
    private const float CruiserDps = 18f;
    private const float CapitalDps = 26f;

    private readonly IBalanceService balance;

    public ShipAiService(IBalanceService balance)
    {
        this.balance = balance;
    }

    internal void UpdateShip(SimulationWorld world, ShipRuntime ship, float deltaSeconds, ICollection<SimulationEventRecord> events)
    {
        if (ship.ReplanCooldownSeconds > 0f)
        {
            ship.ReplanCooldownSeconds = MathF.Max(0f, ship.ReplanCooldownSeconds - deltaSeconds);
        }

        var previousState = ship.State;
        var previousPlanId = ship.ActivePlan?.Id;
        var previousStepId = GetCurrentStep(ship.ActivePlan)?.Id;

        EnsurePlan(world, ship, events);
        ExecutePlan(world, ship, deltaSeconds, events);
        TrackHistory(ship);
        EmitStateEvents(ship, previousState, previousPlanId, previousStepId, events);
    }

    private void EnsurePlan(SimulationWorld world, ShipRuntime ship, ICollection<SimulationEventRecord> events)
    {
        var emergencyPlan = BuildEmergencyPlan(world, ship);
        if (emergencyPlan is not null)
        {
            ship.LastReplanReason = "rule-safety";
            ReplacePlan(ship, emergencyPlan, "rule-safety", events);
            return;
        }

        SyncBehaviorOrders(world, ship);
        var topOrder = GetTopOrder(ship);
        if (topOrder is not null && topOrder.Status == OrderStatus.Queued)
        {
            topOrder.Status = OrderStatus.Active;
        }

        var desiredSourceKind = topOrder is null ? AiPlanSourceKind.DefaultBehavior : AiPlanSourceKind.Order;
        var desiredSourceId = topOrder?.Id ?? ResolveBehaviorSource(world, ship).SourceId;
        var currentPlan = ship.ActivePlan;

        if (currentPlan is not null
            && currentPlan.Status is not AiPlanStatus.Completed and not AiPlanStatus.Failed and not AiPlanStatus.Interrupted
            && currentPlan.SourceKind == desiredSourceKind
            && string.Equals(currentPlan.SourceId, desiredSourceId, StringComparison.Ordinal)
            && !ship.NeedsReplan)
        {
            return;
        }

        if (ship.ReplanCooldownSeconds > 0f && currentPlan is null)
        {
            return;
        }

        ShipPlanRuntime? nextPlan = desiredSourceKind == AiPlanSourceKind.Order
          ? BuildOrderPlan(world, ship, topOrder!)
          : BuildBehaviorFallbackPlan(world, ship);

        if (nextPlan is null)
        {
            nextPlan = CreateIdlePlan(ship, desiredSourceKind, desiredSourceId, "No viable plan");
        }

        if (nextPlan.Kind != Idle)
        {
            ship.LastAccessFailureReason = null;
        }

        ReplacePlan(ship, nextPlan, "replanned", events);
    }

    private void ExecutePlan(SimulationWorld world, ShipRuntime ship, float deltaSeconds, ICollection<SimulationEventRecord> events)
    {
        var plan = ship.ActivePlan;
        if (plan is null)
        {
            ship.State = ShipState.Idle;
            ship.TargetPosition = ship.Position;
            return;
        }

        if (plan.CurrentStepIndex >= plan.Steps.Count)
        {
            CompletePlan(ship, plan, events);
            return;
        }

        plan.UpdatedAtUtc = DateTimeOffset.UtcNow;

        var step = plan.Steps[plan.CurrentStepIndex];
        if (step.Status == AiPlanStepStatus.Planned)
        {
            step.Status = AiPlanStepStatus.Running;
        }

        if (step.CurrentSubTaskIndex >= step.SubTasks.Count)
        {
            CompleteStep(plan, step);
            return;
        }

        var subTask = step.SubTasks[step.CurrentSubTaskIndex];
        if (subTask.Status == WorkStatus.Pending)
        {
            subTask.Status = WorkStatus.Active;
        }
        else if (subTask.Status == WorkStatus.Blocked)
        {
            step.Status = AiPlanStepStatus.Blocked;
            step.BlockingReason = subTask.BlockingReason;
            plan.Status = AiPlanStatus.Blocked;
            ship.State = ShipState.Blocked;
            ship.TargetPosition = subTask.TargetPosition ?? ship.Position;
            return;
        }

        plan.Status = AiPlanStatus.Running;

        var outcome = UpdateSubTask(world, ship, step, subTask, deltaSeconds);
        switch (outcome)
        {
            case SubTaskOutcome.Active:
                step.Status = subTask.Status == WorkStatus.Blocked ? AiPlanStepStatus.Blocked : AiPlanStepStatus.Running;
                plan.Status = subTask.Status == WorkStatus.Blocked ? AiPlanStatus.Blocked : AiPlanStatus.Running;
                return;
            case SubTaskOutcome.Completed:
                subTask.Status = WorkStatus.Completed;
                subTask.Progress = 1f;
                step.CurrentSubTaskIndex += 1;
                step.BlockingReason = null;
                if (step.CurrentSubTaskIndex >= step.SubTasks.Count)
                {
                    CompleteStep(plan, step);
                }

                return;
            case SubTaskOutcome.Failed:
                subTask.Status = WorkStatus.Failed;
                step.Status = AiPlanStepStatus.Failed;
                plan.Status = AiPlanStatus.Failed;
                plan.FailureReason = subTask.BlockingReason ?? "subtask-failed";
                ship.NeedsReplan = true;
                ship.ReplanCooldownSeconds = 0.5f;
                ship.LastReplanReason = plan.FailureReason;
                return;
        }
    }

    private static void CompleteStep(ShipPlanRuntime plan, ShipPlanStepRuntime step)
    {
        step.Status = AiPlanStepStatus.Completed;
        step.BlockingReason = null;
        plan.CurrentStepIndex += 1;
        if (plan.CurrentStepIndex >= plan.Steps.Count)
        {
            plan.Status = AiPlanStatus.Completed;
        }
    }

    private static void CompletePlan(ShipRuntime ship, ShipPlanRuntime plan, ICollection<SimulationEventRecord> events)
    {
        plan.Status = AiPlanStatus.Completed;
        var completedOrder = plan.SourceKind == AiPlanSourceKind.Order
          ? ship.OrderQueue.FirstOrDefault(order => order.Id == plan.SourceId)
          : null;
        if (completedOrder is not null)
        {
            completedOrder.Status = OrderStatus.Completed;
            ship.OrderQueue.RemoveAll(order => order.Id == completedOrder.Id);
            if (completedOrder.SourceKind == ShipOrderSourceKind.Behavior
                && string.Equals(completedOrder.SourceId, RepeatOrders, StringComparison.Ordinal)
                && ship.DefaultBehavior.RepeatOrders.Count > 0)
            {
                ship.DefaultBehavior.RepeatIndex = (ship.DefaultBehavior.RepeatIndex + 1) % ship.DefaultBehavior.RepeatOrders.Count;
            }
        }
        ship.ActivePlan = null;
        ship.NeedsReplan = true;
        ship.ReplanCooldownSeconds = 0.25f;
        ship.LastReplanReason = "plan-completed";
        events.Add(new SimulationEventRecord("ship", ship.Id, "plan-completed", $"{ship.Definition.Name} completed {plan.Kind}.", DateTimeOffset.UtcNow));
    }

    private void ReplacePlan(ShipRuntime ship, ShipPlanRuntime nextPlan, string reason, ICollection<SimulationEventRecord> events)
    {
        if (ship.ActivePlan is not null && ship.ActivePlan.Status is not AiPlanStatus.Completed and not AiPlanStatus.Failed)
        {
            ship.ActivePlan.Status = AiPlanStatus.Interrupted;
            ship.ActivePlan.InterruptReason = reason;
        }

        ship.ActivePlan = nextPlan;
        ship.NeedsReplan = false;
        ship.ReplanCooldownSeconds = 0f;
        ship.LastReplanReason = reason;
        events.Add(new SimulationEventRecord("ship", ship.Id, "plan-updated", $"{ship.Definition.Name} planned {nextPlan.Kind}.", DateTimeOffset.UtcNow));
    }
}
