//
// Filter obviously bad commands for performance
//

#include "AICommandFilter.hpp"

#include <algorithm>

#include "src/main/cpp/net/eagle0/shardok/library/BattalionType.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexCubeUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"

namespace shardok {

using fb::Unit;
using net::eagle0::shardok::common::CommandType;

CoordsSet AICommandFilter::BuildEnemyLocations(const GameStateW& gameState, PlayerId pid) {
    CoordsSet enemyLocations(gameState->hex_map());
    const auto* units = gameState->units();

    for (int i = 0; i < units->size(); ++i) {
        const auto* unit = units->Get(i);
        if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            unit->player_id() != pid && !unit->hidden() && unit->location().column() != -1) {
            enemyLocations.Add(unit->location());
        }
    }

    return enemyLocations;
}

std::vector<size_t> AICommandFilter::FilterCommands(
        const CommandListSPtr& commands,
        PlayerId pid,
        bool isDefender,
        const GameStateW& gameState,
        const SettingsGetter& settings,
        const APDCache& apdCache) {
    std::vector<size_t> filteredIndices;
    filteredIndices.reserve(commands->size());

    // Build enemy and castle locations once for efficiency
    const CoordsSet enemyLocations = BuildEnemyLocations(gameState, pid);
    const CoordsSet castleLocations = AllCastleCoords(gameState->hex_map());

    // Calculate minimum distance to enemies once for all filters
    const double minDistToEnemies = MinDistanceToEnemyUnits(gameState, pid, enemyLocations);

    for (size_t i = 0; i < commands->size(); ++i) {
        const auto& cmd = (*commands)[i];

        // Always allow END_TURN commands
        if (cmd->GetCommandType() == CommandType::END_TURN_COMMAND) {
            filteredIndices.push_back(i);
            continue;
        }

        // Filter obviously bad moves
        bool shouldFilter = false;

        // Check spell preparation waste
        if (IsWastefulAction(
                    *cmd,
                    pid,
                    isDefender,
                    gameState,
                    settings,
                    apdCache,
                    enemyLocations,
                    castleLocations,
                    minDistToEnemies)) {
            shouldFilter = true;
        }

        // Check movement waste
        if (!shouldFilter && IsWastefulMovement(
                                     *cmd,
                                     pid,
                                     isDefender,
                                     gameState,
                                     settings,
                                     apdCache,
                                     enemyLocations,
                                     minDistToEnemies)) {
            shouldFilter = true;
        }

        // Check strategic blunders
        if (!shouldFilter &&
            IsStrategicBlunder(*cmd, pid, isDefender, gameState, settings, minDistToEnemies)) {
            shouldFilter = true;
        }

        if (!shouldFilter) { filteredIndices.push_back(i); }
    }

    return filteredIndices;
}

bool AICommandFilter::IsWastefulAction(
        const ShardokCommand& cmd,
        PlayerId pid,
        bool isDefender,
        const GameStateW& gameState,
        const SettingsGetter& settings,
        const APDCache& apdCache,
        const CoordsSet& enemyLocations,
        const CoordsSet& castleLocations,
        double minDistToEnemies) {
    // Handle different spell types
    switch (cmd.GetCommandType()) {
        case CommandType::METEOR_START_COMMAND: {
            // Meteor preparation filtering
            // Meteor takes 3 rounds (start -> target -> cast) and locks the mage in place

            // Asymmetric filtering based on attacker vs defender role
            if (!isDefender) {
                // Attackers: Don't start meteor when too far from enemies OR castles
                // Check distance to castles as well since meteor can deny castle access
                double minDistToCastles = MinDistanceToCastles(gameState, pid, castleLocations);

                // More aggressive filtering for attackers: filter if >4 hexes from targets
                // Meteor has range 3, so being >4 hexes from enemies AND castles is wasteful
                if (minDistToEnemies > 4.0 && minDistToCastles > 4.0) {
                    return true;  // Too far from enemies and castles, advance first
                }
            }
            // Defenders: Allow meteor in most cases since it's great for area denial
            break;
        }

        case CommandType::START_FIRE_COMMAND: {
            // Fire spell filtering - be very restrictive for attackers
            // Fire only affects adjacent tiles and lasts multiple rounds

            if (!isDefender) {
                // Attackers: Only allow fire if the target location is on or adjacent to an enemy
                const auto cmdProto = cmd.GetCommandProto();
                if (!cmdProto.has_target()) {
                    return true;  // Can't analyze without target info
                }

                const auto& targetCoords = cmdProto.target();
                const Coords fireLocation{
                        static_cast<int8_t>(targetCoords.row()),
                        static_cast<int8_t>(targetCoords.column())};

                // Check if any enemy is on the fire location or adjacent to it
                bool enemyNearFireLocation = false;

                // First check the fire location itself
                if (enemyLocations.Contains(fireLocation)) {
                    enemyNearFireLocation = true;
                } else {
                    // Check adjacent tiles (at most 6 coordinates)
                    const auto& adjacentCoords =
                            HexMapUtils::GetAdjacentCoords(gameState->hex_map(), fireLocation);
                    for (const auto& adjCoord : adjacentCoords) {
                        if (enemyLocations.Contains(adjCoord)) {
                            enemyNearFireLocation = true;
                            break;
                        }
                    }
                }

                if (!enemyNearFireLocation) {
                    return true;  // No enemies on or adjacent to fire location, fire would be
                                  // wasteful
                }
            }
            // Defenders: Allow fire for area denial
            break;
        }

        case CommandType::FORTIFY_COMMAND: {
            // Fortify filtering - attackers shouldn't fortify when far from objectives
            // Fortify improves defense but also allows an engineer to use a Reduce command next

            if (!isDefender) {
                // Attackers: Only allow fortify if within 3 hexes of enemies or castles
                const auto cmdProto = cmd.GetCommandProto();
                if (!cmdProto.has_actor()) {
                    return true;  // Can't analyze without actor info
                }

                const auto unitId = cmdProto.actor().value();

                // Get the acting unit directly by ID
                const Unit* actingUnit = gameState->units()->Get(unitId);
                // verify the unit is still active
                if (actingUnit &&
                    actingUnit->status() !=
                            net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) {
                    actingUnit = nullptr;  // Not a valid unit
                }
                // Verify it's our unit (not enemy)
                if (actingUnit && actingUnit->player_id() != pid) { actingUnit = nullptr; }

                if (!actingUnit) {
                    return true;  // Unit not found or belongs to enemy
                }

                const auto& unitCoords = actingUnit->location();
                const Cube unitCube = OffsetToCube(unitCoords);

                // Check if within 3 hexes of any enemy
                bool nearObjective = false;
                for (const auto& enemyCoords : enemyLocations) {
                    const Cube enemyCube = OffsetToCube(enemyCoords);
                    if (const int hexDistance = CubeDistance(unitCube, enemyCube);
                        hexDistance <= 3) {
                        nearObjective = true;
                        break;
                    }
                }

                // If not near enemies, check if near castles
                if (!nearObjective) {
                    for (const auto& castleCoord : castleLocations) {
                        const Cube castleCube = OffsetToCube(castleCoord);
                        const int hexDistance = CubeDistance(unitCube, castleCube);
                        if (hexDistance <= 3) {
                            nearObjective = true;
                            break;
                        }
                    }
                }

                if (!nearObjective) {
                    return true;  // Too far from enemies and castles, fortify is wasteful for
                                  // attacker
                }
            }
            // Defenders: Allow fortify in most cases since it's about holding positions
            break;
        }

        case CommandType::BUILD_BRIDGE_COMMAND:
        case CommandType::FREEZE_WATER_COMMAND: {
            // Bridge/freeze filtering - only allow if it creates significant tactical shortcuts
            // These actions can fail, so we need high confidence of benefit (8+ action points
            // saved)

            const auto cmdProto = cmd.GetCommandProto();
            if (!cmdProto.has_actor() || !cmdProto.has_target()) {
                return true;  // Can't analyze without full command info
            }

            const auto unitId = cmdProto.actor().value();
            const auto& targetCoords = cmdProto.target();
            const Coords waterLocation{
                    static_cast<int8_t>(targetCoords.row()),
                    static_cast<int8_t>(targetCoords.column())};

            // Get the acting unit directly by ID
            const Unit* actingUnit = gameState->units()->Get(unitId);
            // Verify the unit is still active
            if (actingUnit &&
                actingUnit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) {
                actingUnit = nullptr;  // Not a valid unit or not ours
            }
            // Verify it's our unit (not enemy)
            if (actingUnit && actingUnit->player_id() != pid) { actingUnit = nullptr; }

            if (!actingUnit) {
                return true;  // Unit not found or belongs to enemy
            }

            // Get action point distances for this unit's battalion type
            const auto& battType = settings.GetBattalionType(actingUnit->battalion().type());
            const auto* apd = apdCache->GetRaw(
                    gameState->hex_map(),
                    ActionPointDistancesCache::GetMapId(gameState->hex_map()),
                    battType,
                    false);

            const auto& casterCoords = actingUnit->location();

            // Check if bridge creates significant shortcuts to any tactical objective
            bool worthwhileShortcut = false;

            // Get tiles on the "other side" of the water (adjacent to bridge location)
            const auto& adjacentTiles =
                    HexMapUtils::GetAdjacentCoords(gameState->hex_map(), waterLocation);

            // Check shortcuts to enemies
            for (const auto& enemyCoords : enemyLocations) {
                const auto currentDistance = apd->Distance(casterCoords, enemyCoords);
                if (currentDistance == ActionPointDistances::IMPOSSIBLE) continue;

                // Check if going via any adjacent tile creates a shortcut
                for (const auto& adjacentCoord : adjacentTiles) {
                    const auto distanceToAdjacent = apd->Distance(casterCoords, adjacentCoord);
                    const auto adjacentToObjective = apd->Distance(adjacentCoord, enemyCoords);

                    if (distanceToAdjacent != ActionPointDistances::IMPOSSIBLE &&
                        adjacentToObjective != ActionPointDistances::IMPOSSIBLE) {
                        // New route: caster -> adjacent tile -> objective (plus ~2 for crossing)
                        const auto newRouteDistance = distanceToAdjacent + adjacentToObjective + 2;

                        if (currentDistance >= newRouteDistance + 8) {  // 8+ action points saved
                            worthwhileShortcut = true;
                            break;
                        }
                    }
                }
                if (worthwhileShortcut) break;
            }

            // Check shortcuts to castles if no enemy shortcut found
            if (!worthwhileShortcut) {
                for (const auto& castleCoord : castleLocations) {
                    const auto currentDistance = apd->Distance(casterCoords, castleCoord);
                    if (currentDistance == ActionPointDistances::IMPOSSIBLE) continue;

                    // Check if going via any adjacent tile creates a shortcut
                    for (const auto& adjacentCoord : adjacentTiles) {
                        const auto distanceToAdjacent = apd->Distance(casterCoords, adjacentCoord);
                        const auto adjacentToObjective = apd->Distance(adjacentCoord, castleCoord);

                        if (distanceToAdjacent != ActionPointDistances::IMPOSSIBLE &&
                            adjacentToObjective != ActionPointDistances::IMPOSSIBLE) {
                            // New route: caster -> adjacent tile -> objective (plus ~2 for
                            // crossing)
                            const auto newRouteDistance =
                                    distanceToAdjacent + adjacentToObjective + 2;

                            if (currentDistance >=
                                newRouteDistance + 8) {  // 8+ action points saved
                                worthwhileShortcut = true;
                                break;
                            }
                        }
                    }
                    if (worthwhileShortcut) break;
                }
            }

            if (!worthwhileShortcut) {
                return true;  // No significant shortcut found, filter out this bridge/freeze
            }
            break;
        }

        case CommandType::REPAIR_COMMAND: {
            // Repair filtering - filter repairs with high integrity targets
            // Note: RepairCommandFactory already filters enemy-occupied targets
            const auto cmdProto = cmd.GetCommandProto();
            if (!cmdProto.has_target()) {
                return true;  // Can't analyze without target info
            }

            const auto& targetCoords = cmdProto.target();
            const Coords repairLocation{
                    static_cast<int8_t>(targetCoords.row()),
                    static_cast<int8_t>(targetCoords.column())};

            // Check terrain modifiers at target location
            const auto* terrain = GetTerrain(gameState->hex_map(), repairLocation);
            const auto& modifier = terrain->modifier();

            // Filter based on integrity thresholds
            if (modifier.bridge().present()) {
                // Bridge integrity filtering: >70% is wasteful
                if (modifier.bridge().integrity() > 70.0f) {
                    return true;  // Bridge integrity too high to justify repair
                }
            } else if (modifier.castle().present()) {
                // Castle integrity filtering: >90% is wasteful
                if (modifier.castle().integrity() > 90.0f) {
                    return true;  // Castle integrity too high to justify repair
                }
            }
            break;
        }

        case CommandType::EXTINGUISH_FIRE_COMMAND: {
            // Extinguish fire filtering - don't extinguish fires on enemy-occupied tiles
            const auto cmdProto = cmd.GetCommandProto();
            if (!cmdProto.has_target()) {
                return true;  // Can't analyze without target info
            }

            const auto& targetCoords = cmdProto.target();
            const Coords fireLocation{
                    static_cast<int8_t>(targetCoords.row()),
                    static_cast<int8_t>(targetCoords.column())};

            // Check if any enemy occupies the fire location - let them burn!
            std::vector<PlayerId> allyPids;  // Empty for now - assume 2-player game
            if (gameState.GetKnownEnemyOccupant(pid, allyPids, fireLocation)) {
                return true;  // Don't extinguish fires under enemies
            }
            break;
        }

        default: return false;  // Don't filter other spell types for now
    }

    return false;
}

bool AICommandFilter::IsWastefulMovement(
        const ShardokCommand& cmd,
        PlayerId pid,
        bool isDefender,
        const GameStateW& gameState,
        const SettingsGetter& settings,
        const APDCache& apdCache,
        const CoordsSet& enemyLocations,
        double minDistToEnemies) {
    if (cmd.GetCommandType() != CommandType::MOVE_COMMAND) { return false; }

    // Only filter attacker movement when already fairly far from enemies
    if (isDefender || minDistToEnemies <= 6.0) {
        return false;  // Don't filter defender movement or when close to enemies
    }

    // Get the command proto to access unit and target information
    const auto cmdProto = cmd.GetCommandProto();

    // Check if we have the required information
    if (!cmdProto.has_actor() || !cmdProto.has_target()) {
        return false;  // Can't analyze without unit and target info
    }

    const auto unitId = cmdProto.actor().value();
    const auto& targetCoords = cmdProto.target();

    // Get the acting unit directly by ID
    const Unit* actingUnit = gameState->units()->Get(unitId);
    // Verify the unit is still active
    if (actingUnit &&
        actingUnit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) {
        actingUnit = nullptr;  // Not a valid unit
    }
    // Verify it's our unit (not enemy)
    if (actingUnit && actingUnit->player_id() != pid) { actingUnit = nullptr; }

    if (!actingUnit) {
        return false;  // Unit not found or belongs to enemy
    }

    const auto& currentCoords = actingUnit->location();
    const Coords targetCoordsFlat{
            static_cast<int8_t>(targetCoords.row()),
            static_cast<int8_t>(targetCoords.column())};

    // Get action point distances for this unit's battalion type
    const auto& battType = settings.GetBattalionType(actingUnit->battalion().type());
    const auto* apd = apdCache->GetRaw(
            gameState->hex_map(),
            ActionPointDistancesCache::GetMapId(gameState->hex_map()),
            battType,
            false);

    // Calculate action point distance from current position to closest enemy
    double currentDistToEnemies = std::numeric_limits<double>::max();
    double targetDistToEnemies = std::numeric_limits<double>::max();

    for (const auto& enemyCoords : enemyLocations) {
        const auto currentDist = apd->Distance(currentCoords, enemyCoords);
        const auto targetDist = apd->Distance(targetCoordsFlat, enemyCoords);

        if (currentDist != ActionPointDistances::IMPOSSIBLE) {
            currentDistToEnemies = std::min(currentDistToEnemies, static_cast<double>(currentDist));
        }
        if (targetDist != ActionPointDistances::IMPOSSIBLE) {
            targetDistToEnemies = std::min(targetDistToEnemies, static_cast<double>(targetDist));
        }
    }

    // Filter movement if it takes us significantly farther from all enemies
    // Only when we're already far away (>6 hexes as checked above)
    if (currentDistToEnemies != std::numeric_limits<double>::max() &&
        targetDistToEnemies != std::numeric_limits<double>::max()) {
        // Filter if move increases distance to enemies by more than 2 action points
        if (targetDistToEnemies > currentDistToEnemies + 2.0) {
            return true;  // Wasteful move away from enemies when already far
        }
    }

    return false;
}

bool AICommandFilter::IsStrategicBlunder(
        const ShardokCommand& cmd,
        PlayerId pid,
        bool isDefender,
        const GameStateW& gameState,
        const SettingsGetter& settings,
        double minDistToEnemies) {
    // Simplified strategic blunder detection for now
    // TODO: Implement proper castle abandonment detection
    // TODO: Use minDistToEnemies for strategic blunder logic
    return false;
}

double AICommandFilter::MinDistanceToEnemyUnits(
        const GameStateW& gameState,
        PlayerId pid,
        const CoordsSet& enemyLocations) {
    // Calculate minimum distance from any player unit to any enemy unit
    double minDistance = std::numeric_limits<double>::max();
    const auto* units = gameState->units();

    for (int i = 0; i < units->size(); ++i) {
        const auto* playerUnit = units->Get(i);
        if (playerUnit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            playerUnit->player_id() == pid) {
            const auto& playerCoords = playerUnit->location();
            const Cube playerCube = OffsetToCube(playerCoords);

            for (const auto& enemyCoords : enemyLocations) {
                const Cube enemyCube = OffsetToCube(enemyCoords);
                const int hexDistance = CubeDistance(playerCube, enemyCube);
                minDistance = std::min(minDistance, static_cast<double>(hexDistance));
            }
        }
    }

    return minDistance == std::numeric_limits<double>::max() ? 0.0 : minDistance;
}

double AICommandFilter::MinDistanceToCastles(
        const GameStateW& gameState,
        PlayerId pid,
        const CoordsSet& castleLocations) {
    // Calculate minimum distance from any player unit to any castle
    double minDistance = std::numeric_limits<double>::max();
    const auto* units = gameState->units();

    if (castleLocations.empty()) {
        return 0.0;  // No castles found
    }

    // Find minimum hex distance from any player unit to any castle
    for (int i = 0; i < units->size(); ++i) {
        const auto* unit = units->Get(i);
        if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            unit->player_id() == pid) {
            const auto& unitCoords = unit->location();
            const Cube unitCube = OffsetToCube(unitCoords);

            for (const auto& castleCoord : castleLocations) {
                const Cube castleCube = OffsetToCube(castleCoord);
                const int hexDistance = CubeDistance(unitCube, castleCube);
                minDistance = std::min(minDistance, static_cast<double>(hexDistance));
            }
        }
    }

    return minDistance == std::numeric_limits<double>::max() ? 0.0 : minDistance;
}

bool AICommandFilter::IsPlayerOutnumbered(
        const GameStateW& gameState,
        PlayerId pid,
        double threshold) {
    const int playerUnitCount = CountPlayerUnits(gameState, pid);
    const int enemyUnitCount = CountPlayerUnits(gameState, 1 - pid);  // Assumes 2-player game

    if (enemyUnitCount == 0) return false;

    const double ratio = static_cast<double>(playerUnitCount) / static_cast<double>(enemyUnitCount);
    return ratio < threshold;
}

int AICommandFilter::CountPlayerUnits(const GameStateW& gameState, PlayerId pid) {
    int count = 0;
    const auto* units = gameState->units();

    for (int i = 0; i < units->size(); ++i) {
        const auto* unit = units->Get(i);
        if (unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            unit->player_id() == pid) {
            count++;
        }
    }

    return count;
}

bool AICommandFilter::WouldAbandonCriticalCastle(
        const ShardokCommand& cmd,
        PlayerId pid,
        const GameStateW& gameState) {
    // Simplified implementation - return false for now
    // TODO: Implement proper castle abandonment detection when API is available
    return false;
}

}  // namespace shardok