"""Map generation for PROTOCOL - station topology and adjacency.

The map is a star topology with CAMP at the center, HALL corridors leading
to regional hubs, and specialized stations (LABs, GATEs, GOAL).

Key concepts:
- Regions: S1, S2, S3 (labs), RIDGE (gates and goal)
- Corridors: HALL nodes create travel distance
- Labs: Connect to hub node at end of corridor
- Ridge: Linear chain of gates leading to GOAL
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from purefn.games.protocol.encoding import encode_byte
from purefn.games.protocol.engine import PROFILE_CONFIGS, Profile
from purefn.games.protocol.prng import DomainPRNG

# Region distances (moves from CAMP to hub)
REGION_DISTANCES = {
    "S1": 2,
    "S2": 3,
    "S3": 3,
    "RIDGE": 4,
}

# Lab counts are sampled from these ranges
LAB_COUNT_RANGES = {
    "S1": (3, 5),
    "S2": (2, 3),
    "S3": (1, 2),
}

# Gate counts per profile
GATE_COUNT_RANGES = {
    Profile.TINY: (1, 1),
    Profile.DEV: (3, 4),
    Profile.STANDARD: (4, 5),
    Profile.HARD: (5, 6),
}

# Lab count overrides for TINY profile (smaller map for faster testing)
TINY_LAB_COUNT_RANGES = {
    "S1": (1, 1),
    "S2": (1, 1),
    "S3": (1, 1),
}

# Corridor distance overrides for TINY profile
TINY_REGION_DISTANCES = {
    "S1": 1,
    "S2": 1,
    "S3": 1,
    "RIDGE": 1,
}


@dataclass
class MapConfig:
    """Configuration for map generation."""

    s1_labs: int
    s2_labs: int
    s3_labs: int
    gates: int
    turn_limit: int


@dataclass
class GeneratedMap:
    """Result of map generation."""

    stations: dict[str, dict[str, Any]]
    adjacency: dict[str, list[str]]
    gates_required: int


def sample_map_config(prng: DomainPRNG, profile: Profile) -> MapConfig:
    """Sample map configuration from PRNG.

    Args:
        prng: Domain PRNG instance
        profile: Game profile

    Returns:
        Map configuration with station counts
    """
    # Use TINY lab counts for TINY profile
    lab_ranges = TINY_LAB_COUNT_RANGES if profile == Profile.TINY else LAB_COUNT_RANGES

    s1_lo, s1_hi = lab_ranges["S1"]
    s2_lo, s2_hi = lab_ranges["S2"]
    s3_lo, s3_hi = lab_ranges["S3"]
    gate_lo, gate_hi = GATE_COUNT_RANGES[profile]

    s1_labs = prng.rand_int("map", s1_lo, s1_hi)
    s2_labs = prng.rand_int("map", s2_lo, s2_hi)
    s3_labs = prng.rand_int("map", s3_lo, s3_hi)
    gates = prng.rand_int("map", gate_lo, gate_hi)

    # Turn limit based on profile
    profile_config = PROFILE_CONFIGS[profile]
    if profile_config.turn_limit_min == profile_config.turn_limit_max:
        turn_limit = profile_config.turn_limit_min
    else:
        turn_limit = prng.rand_int(
            "map", profile_config.turn_limit_min, profile_config.turn_limit_max
        )

    return MapConfig(
        s1_labs=s1_labs,
        s2_labs=s2_labs,
        s3_labs=s3_labs,
        gates=gates,
        turn_limit=turn_limit,
    )


def _format_station_id(prefix: str, index: int) -> str:
    """Format station ID with zero-padded index."""
    return f"{prefix}_{index:02d}"


@dataclass
class MapBuilder:
    """Builder for constructing the map topology."""

    stations: dict[str, dict[str, Any]] = field(default_factory=dict)
    adjacency: dict[str, list[str]] = field(default_factory=dict)

    def add_station(self, station_id: str, station_data: dict[str, Any]) -> None:
        """Add a station to the map."""
        self.stations[station_id] = station_data
        if station_id not in self.adjacency:
            self.adjacency[station_id] = []

    def connect(self, station_a: str, station_b: str) -> None:
        """Connect two stations (bidirectional)."""
        if station_a not in self.adjacency:
            self.adjacency[station_a] = []
        if station_b not in self.adjacency:
            self.adjacency[station_b] = []

        if station_b not in self.adjacency[station_a]:
            self.adjacency[station_a].append(station_b)
        if station_a not in self.adjacency[station_b]:
            self.adjacency[station_b].append(station_a)


def _create_corridor(
    builder: MapBuilder,
    region: str,
    distance: int,
    connect_from: str,
) -> str:
    """Create a corridor of HALL nodes.

    Args:
        builder: Map builder
        region: Region name (S1, S2, S3, RIDGE)
        distance: Number of HALL nodes
        connect_from: Station to connect first HALL to

    Returns:
        ID of the last HALL node (the hub)
    """
    prev_station = connect_from

    for i in range(1, distance + 1):
        hall_id = _format_station_id(f"HALL_{region}", i)
        builder.add_station(hall_id, {"type": "HALL"})
        builder.connect(prev_station, hall_id)
        prev_station = hall_id

    return prev_station


def _create_labs(
    builder: MapBuilder,
    region: str,
    count: int,
    hub_id: str,
    profile_config: Any,
    channel_configs: list[dict[str, Any]],
) -> None:
    """Create lab stations connected to hub.

    Args:
        builder: Map builder
        region: Region name (S1, S2, S3)
        count: Number of labs to create
        hub_id: Hub station to connect labs to
        profile_config: Profile configuration for credits
        channel_configs: List of channel configurations for each lab
    """
    lab_type = f"LAB_{region}"

    for i in range(1, count + 1):
        lab_id = _format_station_id(f"LAB_{region}", i)
        channel = channel_configs[i - 1] if i - 1 < len(channel_configs) else {"kind": "hi4"}

        lab_data = {
            "type": lab_type,
            "channel": channel,
            "credits": profile_config.probe_cap,
            "cap": profile_config.probe_cap,
            "regen_every": profile_config.probe_regen_every,
        }
        builder.add_station(lab_id, lab_data)
        builder.connect(hub_id, lab_id)


def _create_ridge(
    builder: MapBuilder,
    gate_count: int,
    hub_id: str,
    profile_config: Any,
    prng: DomainPRNG,
) -> None:
    """Create the ridge with gates and GOAL.

    Args:
        builder: Map builder
        gate_count: Number of gates
        hub_id: Hub station (end of RIDGE corridor)
        profile_config: Profile configuration for attempts
        prng: PRNG for gate parameters
    """
    prev_station = hub_id

    for i in range(1, gate_count + 1):
        gate_id = _format_station_id("GATE", i)

        # Sample gate parameters
        challenge = prng.rand_u8(f"gate:{i:02d}:challenge")
        salt = prng.rand_u8(f"gate:{i:02d}:salt")

        gate_data = {
            "type": "GATE",
            "challenge": encode_byte(challenge),
            "salt": encode_byte(salt),
            "attempts_remaining": profile_config.gate_attempts,
            "unlocked": False,
        }
        builder.add_station(gate_id, gate_data)
        builder.connect(prev_station, gate_id)
        prev_station = gate_id

    # Create GOAL
    builder.add_station("GOAL", {"type": "GOAL"})
    builder.connect(prev_station, "GOAL")


def _generate_channel_configs(
    prng: DomainPRNG,
    region: str,
    count: int,
    profile: Profile,
) -> list[dict[str, Any]]:
    """Generate channel configurations for labs in a region.

    Args:
        prng: Domain PRNG
        region: Region name
        count: Number of labs
        profile: Game profile

    Returns:
        List of channel configurations
    """
    configs: list[dict[str, Any]] = []

    # Channel types available per profile
    if profile == Profile.DEV:
        # DEV: 4-bit channels (hi4, lo4)
        channel_types = ["hi4", "lo4"]
    elif profile == Profile.STANDARD:
        # STANDARD: 2-4 bit channels
        channel_types = ["hi4", "lo4", "parity_pair", "popcount"]
    else:
        # HARD: 1-3 bit channels
        channel_types = ["parity", "parity_pair", "popcount"]

    for i in range(count):
        channel_idx = prng.rand_int(f"channel:{region}:{i}", 0, len(channel_types) - 1)
        channel_kind = channel_types[channel_idx]

        if channel_kind == "parity":
            mask = prng.rand_u8(f"channel:{region}:{i}:mask")
            configs.append({"kind": "parity", "mask": encode_byte(mask)})
        elif channel_kind == "parity_pair":
            mask1 = prng.rand_u8(f"channel:{region}:{i}:mask1")
            mask2 = prng.rand_u8(f"channel:{region}:{i}:mask2")
            configs.append(
                {
                    "kind": "parity_pair",
                    "masks": [encode_byte(mask1), encode_byte(mask2)],
                }
            )
        elif channel_kind == "popcount":
            mask = prng.rand_u8(f"channel:{region}:{i}:mask")
            configs.append({"kind": "popcount", "mask": encode_byte(mask)})
        else:
            configs.append({"kind": channel_kind})

    return configs


def generate_map(prng: DomainPRNG, profile: Profile) -> GeneratedMap:
    """Generate complete map topology.

    Args:
        prng: Domain PRNG instance
        profile: Game profile

    Returns:
        Generated map with stations, adjacency, and gate count
    """
    config = sample_map_config(prng, profile)
    profile_config = PROFILE_CONFIGS[profile]
    builder = MapBuilder()

    # Use TINY distances for TINY profile
    distances = TINY_REGION_DISTANCES if profile == Profile.TINY else REGION_DISTANCES

    # Create CAMP
    builder.add_station("CAMP", {"type": "CAMP"})

    # Create corridors for each region
    s1_hub = _create_corridor(builder, "S1", distances["S1"], "CAMP")
    s2_hub = _create_corridor(builder, "S2", distances["S2"], "CAMP")
    s3_hub = _create_corridor(builder, "S3", distances["S3"], "CAMP")
    ridge_hub = _create_corridor(builder, "RIDGE", distances["RIDGE"], "CAMP")

    # Generate channel configs for each region
    s1_channels = _generate_channel_configs(prng, "S1", config.s1_labs, profile)
    s2_channels = _generate_channel_configs(prng, "S2", config.s2_labs, profile)
    s3_channels = _generate_channel_configs(prng, "S3", config.s3_labs, profile)

    # Create labs
    _create_labs(builder, "S1", config.s1_labs, s1_hub, profile_config, s1_channels)
    _create_labs(builder, "S2", config.s2_labs, s2_hub, profile_config, s2_channels)
    _create_labs(builder, "S3", config.s3_labs, s3_hub, profile_config, s3_channels)

    # Create ridge (gates and GOAL)
    _create_ridge(builder, config.gates, ridge_hub, profile_config, prng)

    return GeneratedMap(
        stations=builder.stations,
        adjacency=builder.adjacency,
        gates_required=config.gates,
    )


def is_gate_blocking_movement(
    from_station: str,
    to_station: str,
    stations: dict[str, dict[str, Any]],
    adjacency: dict[str, list[str]],
) -> tuple[bool, str | None]:
    """Check if movement is blocked by a locked gate.

    Movement past a locked gate on the ridge is not allowed.
    - Moving ONTO a gate is always allowed
    - Moving FROM a locked gate to another gate or GOAL is blocked

    Args:
        from_station: Current station ID
        to_station: Target station ID
        stations: Station data dict
        adjacency: Adjacency dict

    Returns:
        Tuple of (is_blocked, blocking_gate_id or None)
    """
    from_data = stations.get(from_station, {})
    to_data = stations.get(to_station, {})

    # If we're not moving FROM a gate, it's allowed
    if from_data.get("type") != "GATE":
        return False, None

    # If the gate we're on is unlocked, it's allowed
    if from_data.get("unlocked", False):
        return False, None

    # We're on a LOCKED gate - check if target is further along ridge
    # (another gate or GOAL)
    if to_data.get("type") in ("GATE", "GOAL"):
        return True, from_station

    return False, None


def get_ridge_order() -> list[str]:
    """Get the ordered list of ridge stations (for validation)."""
    # This is a helper - actual order depends on gate count
    return ["HALL_RIDGE_04"]  # Gates and GOAL are added dynamically
