import unreal


MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"

EXPECTED_PLAYABLE_ACTORS = {
    "AGR_DemoPlayerStart": {
        "class": unreal.PlayerStart,
    },
    "AGR_DemoWoodResource_01": {
        "class_path": "/Game/Agrarian/Blueprints/Resources/BP_WoodResourceNode",
        "properties": {
            "remaining_harvests": 16,
            "quantity_per_harvest": 2,
            "persistence_node_id": "AGR_DemoWoodResource_01",
        },
    },
    "AGR_DemoFiberResource_01": {
        "class_path": "/Game/Agrarian/Blueprints/Resources/BP_FiberResourceNode",
        "properties": {
            "remaining_harvests": 10,
            "quantity_per_harvest": 3,
            "persistence_node_id": "AGR_DemoFiberResource_01",
        },
    },
    "AGR_DemoCampfire_01": {
        "class_path": "/Game/Agrarian/Blueprints/Structures/BP_Campfire",
        "properties": {
            "fuel_seconds": 180.0,
            "warmth_radius": 650.0,
        },
    },
    "AGR_DemoPrimitiveShelter_01": {
        "class_path": "/Game/Agrarian/Blueprints/Structures/BP_PrimitiveShelter",
        "properties": {
            "weather_protection": 0.7,
        },
    },
    "AGR_DemoRabbitWildlife_01": {
        "class_path": "/Game/Agrarian/Blueprints/Wildlife/BP_RabbitWildlife",
        "properties": {
            "wildlife_id": "rabbit",
            "max_health": 12.0,
        },
    },
    "AGR_GZ_FreshWaterSource_01": {
        "class_path": "/Game/Agrarian/Blueprints/World/BP_FreshWaterSource",
    },
    "AGR_GroundZeroMapBoundary": {
        "class": unreal.AgrarianMapBoundaryVolume,
        "properties": {
            "boundary_id": "ground_zero_mvp_tile",
            "clamp_players_at_boundary": True,
        },
    },
    "AGR_DemoSkyLightingController": {
        "class": unreal.AgrarianSkyLightingController,
    },
    "AGR_DemoWeatherAudioController": {
        "class": unreal.AgrarianWeatherAudioController,
    },
    "AGR_DemoNoticeActor": {
        "class": unreal.AgrarianDemoNoticeActor,
    },
}

LEGACY_FLAT_TEST_LABELS = {
    "AGR_WoodResourceNode_01",
    "AGR_FiberResourceNode_01",
    "AGR_Campfire_01",
    "AGR_PrimitiveShelter_01",
    "AGR_RabbitWildlife_01",
}

NEAR_SPAWN_ACTORS = {
    "AGR_DemoWoodResource_01": 12000.0,
    "AGR_DemoFiberResource_01": 12000.0,
    "AGR_DemoCampfire_01": 12000.0,
    "AGR_DemoPrimitiveShelter_01": 12000.0,
    "AGR_DemoRabbitWildlife_01": 12000.0,
    "AGR_GZ_FreshWaterSource_01": 25000.0,
}


def nearly_equal(left, right):
    return abs(float(left) - float(right)) < 0.001


def distance_2d(left, right):
    return ((left.x - right.x) ** 2.0 + (left.y - right.y) ** 2.0) ** 0.5


def class_path(actor):
    return actor.get_class().get_path_name()


def get_actor_label(actor):
    try:
        return actor.get_actor_label()
    except Exception:
        return actor.get_name()


def verify_class(label, actor, expected, failures):
    expected_class = expected.get("class")
    if expected_class and not isinstance(actor, expected_class):
        failures.append(f"{label} expected class {expected_class.__name__}, got {actor.get_class().get_name()}")

    expected_class_path = expected.get("class_path")
    if expected_class_path:
        actual_class_path = class_path(actor)
        if not actual_class_path.startswith(expected_class_path + "."):
            failures.append(f"{label} expected class under {expected_class_path}, got {actual_class_path}")


def main():
    if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
        raise RuntimeError(f"Could not load map: {MAP_PATH}")

    failures = []
    actors_by_label = {get_actor_label(actor): actor for actor in unreal.EditorLevelLibrary.get_all_level_actors()}

    for legacy_label in LEGACY_FLAT_TEST_LABELS:
        if legacy_label in actors_by_label:
            failures.append(f"legacy flat-test actor should not remain in Ground Zero map: {legacy_label}")

    for label, expected in EXPECTED_PLAYABLE_ACTORS.items():
        actor = actors_by_label.get(label)
        if not actor:
            failures.append(f"{label} missing")
            continue

        verify_class(label, actor, expected, failures)

        for property_name, expected_value in expected.get("properties", {}).items():
            actual_value = actor.get_editor_property(property_name)
            if isinstance(expected_value, (int, float)):
                if not nearly_equal(actual_value, expected_value):
                    failures.append(f"{label} {property_name} expected {expected_value}, got {actual_value}")
            elif isinstance(expected_value, bool):
                if bool(actual_value) != expected_value:
                    failures.append(f"{label} {property_name} expected {expected_value}, got {actual_value}")
            elif str(actual_value) != expected_value:
                failures.append(f"{label} {property_name} expected {expected_value}, got {actual_value}")

    player_start = actors_by_label.get("AGR_DemoPlayerStart")
    if player_start:
        spawn_location = player_start.get_actor_location()
        if spawn_location.z < 150.0:
            failures.append(f"AGR_DemoPlayerStart expected safe above-terrain offset, got z {spawn_location.z}")

        for label, max_distance in NEAR_SPAWN_ACTORS.items():
            actor = actors_by_label.get(label)
            if not actor:
                continue
            actual_distance = distance_2d(spawn_location, actor.get_actor_location())
            if actual_distance > max_distance:
                failures.append(f"{label} is {actual_distance:.0f}cm from spawn, above {max_distance:.0f}cm playable radius")

    if failures:
        raise RuntimeError("Test map placement verification failed: " + "; ".join(failures))

    unreal.log(
        "Agrarian Ground Zero playable test map verification complete: "
        f"{len(EXPECTED_PLAYABLE_ACTORS)} critical actors checked."
    )


main()
