#include "catch.hpp"

#include <algorithm>

#include "Building.h"
#include "BuildingConfig.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "ConfigLoader.h"
#include "GameConfig.h"
#include "Rotation.h"
#include "ShipLayout.h"
#include "ShipsConfig.h"
#include "Simulation.h"
#include "SimulationTestAccess.h"
#include "TestConfig.h"

// readBuildingConfig underpins the copy-settings gesture (REQ-BLD-COPY-CONFIG):
// it extracts a building's recipe / schematic / layout / splitter filters so they
// can be stamped onto a same-type building. It reads operational buildings and
// construction sites alike.

namespace
{
const BuildingDef* findDef(const GameConfig& cfg, BuildingType type)
{
    for (const BuildingDef& def : cfg.buildings.buildings)
    {
        if (def.type == type) { return &def; }
    }
    return nullptr;
}

BuildingId placeOperational(Simulation& sim, const GameConfig& cfg,
                            BuildingType type, QPoint anchor)
{
    const BuildingDef* def = findDef(cfg, type);
    REQUIRE(def != nullptr);
    return SimulationTestAccess::buildings(sim).placeImmediate(
        type, def->surfaceMask, anchor, Rotation::East);
}

const ShipDef* findAvailableSchematic(const GameConfig& cfg)
{
    for (const ShipDef& def : cfg.ships.ships)
    {
        // A ship starts unlocked iff no unlock group grants it (REQ-LOCK-EXPLICIT).
        bool granted = false;
        for (const UnlockGroupDef& group : cfg.unlocks.groups)
        {
            if (std::find(group.ships.begin(), group.ships.end(), def.id) != group.ships.end())
            {
                granted = true;
                break;
            }
        }
        if (!granted) { return &def; }
    }
    return nullptr;
}
}  // namespace

TEST_CASE("readBuildingConfig returns a miner's selected recipe", "[copyconfig]")
{
    const GameConfig cfg = loadTestConfig();
    Simulation sim(loadTestConfig(), 7);

    const BuildingId id = placeOperational(sim, cfg, BuildingType::Miner, QPoint(0, 0));
    SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");

    const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
    REQUIRE(config.has_value());
    CHECK(config->type == BuildingType::Miner);
    REQUIRE(config->recipeId.has_value());
    CHECK(*config->recipeId == "mine_iron_ore");
    CHECK_FALSE(config->isSplitter);
    CHECK_FALSE(config->shipLayout.has_value());
}

TEST_CASE("readBuildingConfig leaves recipe unset when nothing is selected",
          "[copyconfig]")
{
    const GameConfig cfg = loadTestConfig();
    Simulation sim(loadTestConfig(), 7);

    const BuildingId id = placeOperational(sim, cfg, BuildingType::Assembler, QPoint(0, 0));

    const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
    REQUIRE(config.has_value());
    CHECK(config->type == BuildingType::Assembler);
    CHECK_FALSE(config->recipeId.has_value());  // nothing to copy
    CHECK_FALSE(config->isSplitter);
}

TEST_CASE("readBuildingConfig returns a shipyard's schematic and layout",
          "[copyconfig]")
{
    const GameConfig cfg = loadTestConfig();
    Simulation sim(loadTestConfig(), 7);

    const ShipDef* schematic = findAvailableSchematic(cfg);
    REQUIRE(schematic != nullptr);

    const BuildingId id = placeOperational(sim, cfg, BuildingType::Shipyard, QPoint(0, 0));
    SimulationTestAccess::buildings(sim).setRecipe(id, schematic->id);
    SimulationTestAccess::buildings(sim).setShipLayout(id, ShipLayoutConfig{});

    const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
    REQUIRE(config.has_value());
    CHECK(config->type == BuildingType::Shipyard);
    REQUIRE(config->recipeId.has_value());
    CHECK(*config->recipeId == schematic->id);
    CHECK(config->shipLayout.has_value());
}

TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]")
{
    const GameConfig cfg = loadTestConfig();
    Simulation sim(loadTestConfig(), 7);

    // A placed miner enters the construction queue as a site (not yet operational).
    const BuildingId id =
        SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
    REQUIRE(id != kInvalidBuildingId);
    REQUIRE(sim.getBuildings().findBuilding(id) == nullptr);
    REQUIRE(sim.getBuildings().findSite(id) != nullptr);

    SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");

    const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
    REQUIRE(config.has_value());
    CHECK(config->type == BuildingType::Miner);
    REQUIRE(config->recipeId.has_value());
    CHECK(*config->recipeId == "mine_iron_ore");
}

TEST_CASE("readBuildingConfig returns nullopt for an unknown id", "[copyconfig]")
{
    Simulation sim(loadTestConfig(), 7);
    CHECK_FALSE(readBuildingConfig(sim, kInvalidBuildingId).has_value());
}
