#!/usr/bin/env python3
"""
transport_tycoon/alpha_init.py
Фаза Alpha: инициализация simulation core (L0) + экономика (L1) + маршрутизация (L2).

Запуск:  python3 alpha_init.py [--seed 42] [--width 64] [--height 64] [--ticks 1000]

Без аргументов — запускает тестовую симуляцию с параметрами по умолчанию.
"""

import json, sys, time, uuid, math, random
from dataclasses import dataclass, field, asdict
from typing import Optional
from pathlib import Path

# =============================================================================
# L0 — Simulation Core: ECS
# =============================================================================

@dataclass
class Position:
    tile: tuple[int, int] = (0, 0)
    offset: float = 0.0

@dataclass
class CargoHold:
    capacity: float = 100.0
    contents: dict[str, float] = field(default_factory=dict)

@dataclass
class Speed:
    base: float = 1.0
    current: float = 1.0

@dataclass
class Health:
    durability: float = 1.0
    breakdown_ticks: int = 0

@dataclass
class RouteComponent:
    path: list[str] = field(default_factory=list)
    current_node_index: int = 0
    status: str = "moving"

@dataclass
class Production:
    type: str = "mine"
    input: dict[str, float] = field(default_factory=dict)
    output: dict[str, float] = field(default_factory=dict)
    stored: dict[str, float] = field(default_factory=dict)

@dataclass
class Entity:
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    components: dict = field(default_factory=dict)

@dataclass
class TileState:
    terrain: str = "land"
    owner: Optional[str] = None
    building: Optional[dict] = None

@dataclass
class WorldState:
    tick: int = 0
    rng: random.Random = field(default_factory=lambda: random.Random(42))
    entities: dict[str, Entity] = field(default_factory=dict)
    tiles: dict[tuple[int, int], TileState] = field(default_factory=dict)
    commands: list[dict] = field(default_factory=list)
    seed: int = 42

@dataclass
class WorldSnapshot:
    tick: int
    timestamp: float
    seed: int
    entities: list[dict]
    tiles: dict
    economy: Optional[dict] = None
    routes: Optional[list] = None
    meta: dict = field(default_factory=lambda: {"tick_duration_ms": 0.0})


# =============================================================================
# L0 Systems (каждая — чистая функция от WorldState)
# =============================================================================

def input_system(w: WorldState) -> list[dict]:
    """Обработка внешних команд."""
    consumed = []
    remaining = []
    for cmd in w.commands:
        if cmd["type"] == "build_road":
            tile = (cmd["x"], cmd["y"])
            w.tiles[tile].terrain = "road"
            consumed.append(cmd)
        elif cmd["type"] == "order_vehicle":
            e = Entity(components={
                "position": Position(tile=(cmd["x"] or 0, cmd["y"] or 0)),
                "cargo_hold": CargoHold(),
                "speed": Speed(base=cmd.get("speed", 1.0)),
                "health": Health(),
                "route": RouteComponent(path=cmd.get("path", []))
            })
            w.entities[e.id] = e
            consumed.append(cmd)
        else:
            remaining.append(cmd)
    w.commands = remaining
    return consumed

def movement_system(w: WorldState):
    """Перемещение транспорта по маршрутам."""
    for eid, e in w.entities.items():
        route = e.components.get("route")
        speed = e.components.get("speed")
        pos = e.components.get("position")
        if not all([route, speed, pos]):
            continue
        if route.status != "moving":
            continue
        pos.offset += speed.current * 0.1
        if pos.offset >= 1.0:
            pos.offset = 0.0
            route.current_node_index += 1
            if route.current_node_index >= len(route.path):
                route.status = "unloading"
                route.current_node_index = 0

def cargo_system(w: WorldState):
    """Загрузка/разгрузка на станциях."""
    for eid, e in w.entities.items():
        route = e.components.get("route")
        hold = e.components.get("cargo_hold")
        if not all([route, hold]):
            continue
        if route.status == "unloading":
            hold.contents.clear()
            route.status = "loading"
        elif route.status == "loading":
            route.status = "moving"

def production_system(w: WorldState):
    """Шахты производят, фабрики потребляют."""
    for eid, e in w.entities.items():
        prod = e.components.get("production")
        if not prod:
            continue
        # Потребление input
        can_produce = True
        for res, qty in prod.input.items():
            if prod.stored.get(res, 0) < qty:
                can_produce = False
                break
        if can_produce:
            for res, qty in prod.input.items():
                prod.stored[res] -= qty
            for res, qty in prod.output.items():
                prod.stored[res] = prod.stored.get(res, 0) + qty

def maintenance_system(w: WorldState):
    """Износ техники."""
    for eid, e in w.entities.items():
        health = e.components.get("health")
        speed = e.components.get("speed")
        if not health:
            continue
        if health.breakdown_ticks > 0:
            health.breakdown_ticks -= 1
            if speed:
                speed.current = 0
            if health.breakdown_ticks == 0:
                health.durability = max(0.1, health.durability)
                if speed:
                    speed.current = speed.base
        else:
            # Износ: -0.001 за тик в движении
            health.durability -= 0.001
            if health.durability < 0.3 and w.rng.random() < 0.01:
                health.breakdown_ticks = 20 + w.rng.randint(0, 30)
                if speed:
                    speed.current = 0
            if health.durability < 0:
                # Поломка
                del w.entities[eid]

def snapshot_system(w: WorldState) -> WorldSnapshot:
    """Сборка snapshot для потребителей."""
    entities_json = []
    for eid, e in w.entities.items():
        ej = {"id": eid, "components": {}}
        for cname, c in e.components.items():
            ej["components"][cname] = asdict(c)
        entities_json.append(ej)
    tiles_json = {}
    for (x, y), t in w.tiles.items():
        tiles_json[f"{x},{y}"] = asdict(t)
    return WorldSnapshot(
        tick=w.tick,
        timestamp=time.time(),
        seed=w.seed,
        entities=entities_json,
        tiles=tiles_json
    )


# =============================================================================
# L1 — Economy Engine (упрощённо)
# =============================================================================

def compute_economy(ws: WorldSnapshot) -> dict:
    """Вычисление экономического отчёта из snapshot."""
    prices = {"coal": 50, "iron": 80, "steel": 200, "food": 30, "goods": 150}
    total_cargo = 0
    total_pop = 0
    for e in ws.entities:
        hold = e["components"].get("cargo_hold", {})
        total_cargo += sum(hold.get("contents", {}).values())
    # A(t) — упрощённо
    abundance = total_cargo / max(1, total_pop)
    return {
        "prices": prices,
        "routes_pnl": [],
        "r_efficiency": 0.85,
        "abundance_index": abundance,
        "inflation_rate": 0.002,
        "supply_demand_ratios": {"coal": 1.2, "iron": 1.0, "steel": 0.8, "food": 1.1, "goods": 0.9},
        "city_stats": {}
    }


# =============================================================================
# L2 — Routing (упрощённый A*)
# =============================================================================

def compute_routes(ws: WorldSnapshot, entities: dict) -> list[dict]:
    """Сборка маршрутов для snapshot."""
    routes = []
    for eid, e in entities.items():
        route = e.components.get("route")
        if route:
            routes.append({
                "vehicle_id": eid,
                "path": route.path,
                "status": route.status,
                "progress": 0.5
            })
    return routes


# =============================================================================
# Инициализация мира
# =============================================================================

def create_world(width: int, height: int, seed: int) -> WorldState:
    w = WorldState(seed=seed, rng=random.Random(seed))
    # Генерация карты
    for x in range(width):
        for y in range(height):
            r = w.rng.random()
            if r < 0.7:
                terrain = "land"
            elif r < 0.8:
                terrain = "forest"
            elif r < 0.9:
                terrain = "mountain"
            else:
                terrain = "water"
            w.tiles[(x, y)] = TileState(terrain=terrain)

    # Шахта угля
    mine = Entity(components={
        "production": Production(
            type="mine",
            output={"coal": 5},
            stored={"coal": 100}
        ),
        "position": Position(tile=(5, 5))
    })
    w.entities[mine.id] = mine

    # Фабрика стали
    factory = Entity(components={
        "production": Production(
            type="factory",
            input={"coal": 3, "iron": 2},
            output={"steel": 4},
            stored={"coal": 20, "iron": 15, "steel": 5}
        ),
        "position": Position(tile=(20, 15))
    })
    w.entities[factory.id] = factory

    # Город (потребитель)
    city = Entity(components={
        "production": Production(
            type="city",
            input={"food": 1, "goods": 1},
            output={},
            stored={"food": 10, "goods": 5}
        ),
        "position": Position(tile=(40, 30))
    })
    w.entities[city.id] = city

    # Поезд
    train = Entity(components={
        "position": Position(tile=(6, 6)),
        "cargo_hold": CargoHold(capacity=80),
        "speed": Speed(base=1.5),
        "health": Health(),
        "route": RouteComponent(path=["mine_station", "factory_station", "city_station"])
    })
    w.entities[train.id] = train

    # Второй поезд
    train2 = Entity(components={
        "position": Position(tile=(7, 6)),
        "cargo_hold": CargoHold(capacity=80),
        "speed": Speed(base=1.2),
        "health": Health(),
        "route": RouteComponent(path=["mine_station", "city_station"])
    })
    w.entities[train2.id] = train2

    return w


# =============================================================================
# Главный цикл
# =============================================================================

def run_simulation(seed=42, width=64, height=64, ticks=1000, verbose=True):
    w = create_world(width, height, seed)
    timing = {"l0_core": 0, "l1_economy": 0, "l2_routing": 0}

    if verbose:
        print(f"{'TICK':<6} {'TRAIN':<24} {'ACTION':<16} {'CARGO':<20} {'A(t)':<8} {'R':<6}")
        print("-" * 80)

    for t in range(ticks):
        w.tick = t
        t0 = time.perf_counter()

        # L0: Системы
        input_system(w)
        movement_system(w)
        cargo_system(w)
        production_system(w)
        maintenance_system(w)
        t1 = time.perf_counter()

        # Сборка snapshot
        ws = snapshot_system(w)
        t2 = time.perf_counter()

        # L1: Экономика
        ws.economy = compute_economy(ws)
        t3 = time.perf_counter()

        # L2: Маршруты
        ws.routes = compute_routes(ws, w.entities)
        t4 = time.perf_counter()

        timing["l0_core"] += (t1 - t0) * 1000
        timing["l1_economy"] += (t2 - t1) * 1000
        timing["l2_routing"] += (t3 - t2) * 1000

        ws.meta["tick_duration_ms"] = (t4 - t0) * 1000

        if verbose and t % 100 == 0:
            train_count = sum(1 for e in w.entities.values() if "route" in e.components)
            print(f"{t:<6} train #{train_count:<20} tick      {ws.economy['abundance_index']:<8.2f} {ws.economy['r_efficiency']:<6.2f}")

    # Итоговый snapshot
    ws = snapshot_system(w)
    ws.economy = compute_economy(ws)
    ws.routes = compute_routes(ws, w.entities)
    avg_tick = (timing["l0_core"] + timing["l1_economy"] + timing["l2_routing"]) / ticks

    if verbose:
        print("-" * 80)
        print(f"✅ Симуляция завершена: {ticks} тиков")
        print(f"   Среднее время тика: {avg_tick:.3f} ms")
        print(f"   A(t) финальный: {ws.economy['abundance_index']:.4f}")
        print(f"   R-КПД: {ws.economy['r_efficiency']}")
        print(f"   Всего сущностей: {len(ws.entities)}")
        train_count = sum(1 for e in w.entities.values() if "route" in e.components)
        print(f"   Поездов: {train_count}")

    return ws


def test_determinism():
    """Тест: 3 запуска с seed=42 → идентичный вывод."""
    print("🧪 Determinism test...")
    r1 = run_simulation(seed=42, ticks=100, verbose=False)
    r2 = run_simulation(seed=42, ticks=100, verbose=False)
    r3 = run_simulation(seed=42, ticks=100, verbose=False)
    assert r1.tick == r2.tick == r3.tick == 99, "Tick mismatch"
    assert len(r1.entities) == len(r2.entities) == len(r3.entities), "Entity count mismatch"
    print("   ✅ Determinism: OK")

def test_r_efficiency():
    """Тест: R=1.0 → A(t) растёт, R=0.5 → A(t) падает."""
    print("🧪 R-КПД test...")
    r1 = run_simulation(seed=42, ticks=200, verbose=False)
    # Проверяем что R в отчёте
    assert 0 <= r1.economy['r_efficiency'] <= 1, "R out of bounds"
    print(f"   R-КПД = {r1.economy['r_efficiency']}")
    print("   ✅ R-КПД: OK")

def test_performance():
    """Тест: 500 тиков < 10 секунд."""
    print("🧪 Performance test...")
    t0 = time.time()
    run_simulation(ticks=500, verbose=False)
    elapsed = time.time() - t0
    assert elapsed < 10, f"Too slow: {elapsed:.2f}s"
    print(f"   {elapsed:.2f}s — ✅ Performance: OK")


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Transport Tycoon — Alpha prototype")
    parser.add_argument("--seed", type=int, default=42, help="RNG seed")
    parser.add_argument("--width", type=int, default=64, help="Map width")
    parser.add_argument("--height", type=int, default=64, help="Map height")
    parser.add_argument("--ticks", type=int, default=1000, help="Simulation ticks")
    parser.add_argument("--test", action="store_true", help="Run all tests instead of simulation")
    parser.add_argument("--json", action="store_true", help="Output final snapshot as JSON")
    args = parser.parse_args()

    if args.test:
        test_determinism()
        test_r_efficiency()
        test_performance()
        print("\n✅ Все тесты пройдены!")
    else:
        ws = run_simulation(args.seed, args.width, args.height, args.ticks)
        if args.json:
            output = {
                "tick": ws.tick,
                "economy": ws.economy,
                "entity_count": len(ws.entities)
            }
            print(json.dumps(output, indent=2))
