"""
The Grid - Main orchestration class.

"The Grid. A digital frontier. I tried to picture clusters of information
as they moved through the computer."
"""

from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Optional, Any
from enum import Enum
import uuid
import yaml
import json

from .cluster import Cluster, ClusterStatus
from .block import Block, BlockStatus
from .thread import Thread, ThreadStatus
from .program import Program, ProgramStatus
from .recognizer import Recognizer, RecognizerType
from .disc import IdentityDisc
from .energy import EnergyManager, EnergyPool
from .io_tower import IOTower, CheckpointReason, CheckpointContext


class GridStatus(Enum):
    """Status of The Grid."""
    INITIALIZING = "initializing"
    READY = "ready"
    RUNNING = "running"
    PAUSED = "paused"
    COMPLETED = "completed"
    FAILED = "failed"


@dataclass
class GridConfig:
    """Configuration for The Grid."""
    # Fission control
    max_depth: int = 4
    max_concurrent: int = 8
    energy_per_spawn: int = 100

    # Energy budgets
    grid_budget: int = 10000
    cluster_budget: int = 2000
    block_budget: int = 500
    thread_budget: int = 100
    low_energy_threshold: float = 0.1

    # I/O Tower
    require_at_depth: int = 3
    require_before_commit: bool = True
    require_on_error: bool = True
    timeout_seconds: int = 300

    # Display
    tron_aesthetics: bool = True

    @classmethod
    def from_yaml(cls, path: Path) -> "GridConfig":
        """Load config from YAML file."""
        with open(path) as f:
            data = yaml.safe_load(f)

        fission = data.get("fission", {})
        energy = data.get("energy", {})
        io = data.get("io_towers", {})
        display = data.get("display", {})

        return cls(
            max_depth=fission.get("max_depth", 4),
            max_concurrent=fission.get("max_concurrent", 8),
            energy_per_spawn=fission.get("energy_per_spawn", 100),
            grid_budget=energy.get("grid_budget", 10000),
            cluster_budget=energy.get("cluster_budget", 2000),
            block_budget=energy.get("block_budget", 500),
            thread_budget=energy.get("thread_budget", 100),
            low_energy_threshold=energy.get("low_energy_threshold", 0.1),
            require_at_depth=io.get("require_at_depth", 3),
            require_before_commit=io.get("require_before_commit", True),
            require_on_error=io.get("require_on_error", True),
            timeout_seconds=io.get("timeout_seconds", 300),
            tron_aesthetics=display.get("tron_aesthetics", True),
        )


class Grid:
    """
    The Grid - Main orchestration class.

    The Grid is the digital frontier where Clusters, Blocks, Threads,
    and Programs execute. It manages the entire execution hierarchy
    and coordinates energy flow.
    """

    def __init__(
        self,
        name: str = "The Grid",
        config: GridConfig = None,
        config_path: Path = None,
    ):
        self.id = str(uuid.uuid4())[:8]
        self.name = name
        self.status = GridStatus.INITIALIZING
        self.created_at = datetime.now()

        # Load configuration
        if config:
            self.config = config
        elif config_path and config_path.exists():
            self.config = GridConfig.from_yaml(config_path)
        else:
            self.config = GridConfig()

        # Energy management
        self.energy = EnergyManager(
            grid_budget=self.config.grid_budget,
            cluster_budget=self.config.cluster_budget,
            block_budget=self.config.block_budget,
            thread_budget=self.config.thread_budget,
            spawn_cost=self.config.energy_per_spawn,
            low_threshold=self.config.low_energy_threshold,
        )

        # I/O Tower (human checkpoints)
        self.io_tower = IOTower(
            require_at_depth=self.config.require_at_depth,
            require_before_commit=self.config.require_before_commit,
            require_on_error=self.config.require_on_error,
            timeout_seconds=self.config.timeout_seconds,
        )

        # Clusters
        self.clusters: dict[str, Cluster] = {}

        # Active Programs (for tracking concurrent execution)
        self.active_programs: dict[str, Program] = {}

        # Statistics
        self.cycles_completed = 0
        self.programs_spawned = 0
        self.programs_derezzed = 0

        self.status = GridStatus.READY

    def create_cluster(
        self,
        name: str,
        purpose: str,
        description: str = "",
    ) -> Cluster:
        """Create a new Cluster on The Grid."""
        cluster = Cluster(
            name=name,
            purpose=purpose,
            description=description,
        )

        # Allocate energy
        cluster.energy_pool = self.energy.create_cluster_pool(cluster.id)

        self.clusters[cluster.id] = cluster
        return cluster

    def get_cluster(self, cluster_id: str) -> Optional[Cluster]:
        """Get a Cluster by ID."""
        return self.clusters.get(cluster_id)

    def get_cluster_by_name(self, name: str) -> Optional[Cluster]:
        """Get a Cluster by name."""
        for cluster in self.clusters.values():
            if cluster.name == name:
                return cluster
        return None

    def spawn_program(
        self,
        name: str,
        purpose: str,
        parent: Program = None,
        energy_budget: int = None,
    ) -> Optional[Program]:
        """Spawn a new Program on The Grid."""
        if energy_budget is None:
            energy_budget = self.config.energy_per_spawn

        # Check concurrent limit
        if len(self.active_programs) >= self.config.max_concurrent:
            return None

        # Check depth limit if parent exists
        depth = 0 if parent is None else parent.depth + 1
        if depth > self.config.max_depth:
            return None

        # Check if I/O Tower checkpoint needed
        if self.io_tower.should_checkpoint(CheckpointReason.FISSION_DEPTH, depth):
            context = CheckpointContext(
                reason=CheckpointReason.FISSION_DEPTH,
                program_id=parent.id if parent else "grid",
                program_name=parent.name if parent else "Grid",
                depth=depth,
                energy_remaining=parent.disc.energy_remaining() if parent else self.energy.total_remaining(),
                energy_allocated=parent.disc.energy_allocated if parent else self.config.grid_budget,
                pending_actions=[f"Spawn Program: {name}"],
                details={"purpose": purpose, "energy_cost": energy_budget},
            )
            # In actual use, this would pause for user input
            # For now, we continue (checkpoint is logged)
            self.io_tower.checkpoint(context)

        program = Program(
            name=name,
            purpose=purpose,
            parent=parent,
            energy_budget=energy_budget,
        )

        self.active_programs[program.id] = program
        self.programs_spawned += 1

        return program

    def derez_program(self, program: Program) -> None:
        """Derez (terminate) a Program."""
        program.derez()
        if program.id in self.active_programs:
            del self.active_programs[program.id]
        self.programs_derezzed += 1

    def start(self) -> None:
        """Start Grid execution."""
        self.status = GridStatus.RUNNING

        # Start all pending clusters
        for cluster in self.clusters.values():
            if cluster.status == ClusterStatus.PENDING:
                cluster.start()

    def pause(self) -> None:
        """Pause Grid execution."""
        self.status = GridStatus.PAUSED

    def resume(self) -> None:
        """Resume Grid execution."""
        if self.status == GridStatus.PAUSED:
            self.status = GridStatus.RUNNING

    def complete_cycle(self) -> None:
        """Complete a cycle of execution."""
        self.cycles_completed += 1

    def is_complete(self) -> bool:
        """Check if all work on The Grid is complete."""
        return all(
            c.status in (ClusterStatus.COMPLETED, ClusterStatus.DEREZZED)
            for c in self.clusters.values()
        )

    def get_status_summary(self) -> dict:
        """Get Grid status summary."""
        return {
            "id": self.id,
            "name": self.name,
            "status": self.status.value,
            "clusters": len(self.clusters),
            "active_programs": len(self.active_programs),
            "programs_spawned": self.programs_spawned,
            "programs_derezzed": self.programs_derezzed,
            "cycles_completed": self.cycles_completed,
            "energy_remaining": self.energy.total_remaining(),
            "energy_consumed": self.energy.total_consumed(),
        }

    def energy_percentage(self) -> float:
        """Get Grid energy as percentage."""
        return self.energy.grid_pool.percentage

    def get_energy_bar(self, width: int = 20) -> str:
        """Get visual energy bar."""
        return self.energy.grid_pool.get_bar(width)

    def render_status(self) -> str:
        """Render Grid status display."""
        lines = [
            "╔" + "═" * 77 + "╗",
            "║" + " " * 77 + "║",
            f"║   GRID STATUS         {self.get_energy_bar()}  {self.energy_percentage():.0f}% Energy" + " " * 22 + "║",
            f"║   Active Programs     {len(self.active_programs):<54}║",
            f"║   Clusters            {len(self.clusters):<54}║",
            f"║   Cycles completed    {self.cycles_completed:<54}║",
            "║" + " " * 77 + "║",
        ]
        return "\n".join(lines)

    def to_dict(self) -> dict:
        """Serialize Grid to dictionary."""
        return {
            "id": self.id,
            "name": self.name,
            "status": self.status.value,
            "created_at": self.created_at.isoformat(),
            "config": {
                "max_depth": self.config.max_depth,
                "max_concurrent": self.config.max_concurrent,
                "grid_budget": self.config.grid_budget,
            },
            "clusters": [c.to_dict() for c in self.clusters.values()],
            "active_programs": [p.to_dict() for p in self.active_programs.values()],
            "statistics": {
                "cycles_completed": self.cycles_completed,
                "programs_spawned": self.programs_spawned,
                "programs_derezzed": self.programs_derezzed,
                "energy_remaining": self.energy.total_remaining(),
                "energy_consumed": self.energy.total_consumed(),
            },
        }

    def to_json(self) -> str:
        """Serialize Grid to JSON."""
        return json.dumps(self.to_dict(), indent=2)

    def __repr__(self) -> str:
        return f"Grid(id={self.id}, name={self.name}, clusters={len(self.clusters)})"
