"""
Block - Collection of Threads working together.

"A Block coordinates Threads toward a common goal."
"""

from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
from enum import Enum
import uuid

from .thread import Thread, ThreadStatus
from .energy import EnergyPool


class BlockStatus(Enum):
    """Status of a Block."""
    PENDING = "pending"
    RUNNING = "running"
    BLOCKED = "blocked"
    COMPLETED = "completed"
    FAILED = "failed"
    DEREZZED = "derezzed"


class Block:
    """
    Block - Collection of Threads (task group).

    Blocks organize related Threads and manage their execution order.
    Threads within a Block can have dependencies on each other.
    """

    def __init__(
        self,
        name: str,
        purpose: str,
        cluster_id: str,
        blocked_by: list[str] = None,
    ):
        self.id = str(uuid.uuid4())[:8]
        self.name = name
        self.purpose = purpose
        self.cluster_id = cluster_id
        self.blocked_by = blocked_by or []

        self.status = BlockStatus.PENDING
        self.created_at = datetime.now()
        self.started_at: Optional[datetime] = None
        self.completed_at: Optional[datetime] = None

        # Threads in this Block
        self.threads: dict[str, Thread] = {}
        self.thread_order: list[str] = []  # Execution order

        # Energy
        self.energy_pool: Optional[EnergyPool] = None

    def add_thread(
        self,
        name: str,
        purpose: str,
        program_type: str = "program",
        blocked_by: list[str] = None,
    ) -> Thread:
        """Add a new Thread to this Block."""
        thread = Thread(
            name=name,
            purpose=purpose,
            block_id=self.id,
            program_type=program_type,
            blocked_by=blocked_by,
        )
        self.threads[thread.id] = thread
        self.thread_order.append(thread.id)
        return thread

    def get_thread(self, thread_id: str) -> Optional[Thread]:
        """Get a Thread by ID."""
        return self.threads.get(thread_id)

    def get_thread_by_name(self, name: str) -> Optional[Thread]:
        """Get a Thread by name."""
        for thread in self.threads.values():
            if thread.name == name:
                return thread
        return None

    def can_start(self, completed_blocks: set[str]) -> bool:
        """Check if all blocking Blocks are completed."""
        if not self.blocked_by:
            return True
        return all(b in completed_blocks for b in self.blocked_by)

    def start(self) -> None:
        """Start Block execution."""
        self.status = BlockStatus.RUNNING
        self.started_at = datetime.now()

    def get_runnable_threads(self) -> list[Thread]:
        """Get Threads that can start (not blocked)."""
        completed = {
            t.name for t in self.threads.values()
            if t.status == ThreadStatus.COMPLETED
        }
        return [
            t for t in self.threads.values()
            if t.status == ThreadStatus.PENDING and t.can_start(completed)
        ]

    def get_running_threads(self) -> list[Thread]:
        """Get currently running Threads."""
        return [
            t for t in self.threads.values()
            if t.status == ThreadStatus.RUNNING
        ]

    def get_completed_threads(self) -> list[Thread]:
        """Get completed Threads."""
        return [
            t for t in self.threads.values()
            if t.status == ThreadStatus.COMPLETED
        ]

    def all_threads_completed(self) -> bool:
        """Check if all Threads are completed."""
        return all(
            t.status in (ThreadStatus.COMPLETED, ThreadStatus.DEREZZED)
            for t in self.threads.values()
        )

    def any_thread_failed(self) -> bool:
        """Check if any Thread failed."""
        return any(
            t.status == ThreadStatus.FAILED
            for t in self.threads.values()
        )

    def complete(self) -> None:
        """Mark Block as completed."""
        self.status = BlockStatus.COMPLETED
        self.completed_at = datetime.now()
        # Return unused energy
        if self.energy_pool:
            self.energy_pool.return_unused()

    def fail(self) -> None:
        """Mark Block as failed."""
        self.status = BlockStatus.FAILED
        self.completed_at = datetime.now()

    def derez(self) -> None:
        """Mark Block as derezzed."""
        self.status = BlockStatus.DEREZZED
        for thread in self.threads.values():
            thread.derez()

    def progress(self) -> float:
        """Get overall Block progress (0-100)."""
        if not self.threads:
            return 0.0
        total_progress = sum(t.progress for t in self.threads.values())
        return total_progress / len(self.threads)

    def energy_consumed(self) -> int:
        """Get total energy consumed by this Block."""
        return sum(
            t.disc.energy_consumed for t in self.threads.values()
        )

    def duration(self) -> Optional[float]:
        """Get execution duration in seconds."""
        if not self.started_at:
            return None
        end = self.completed_at or datetime.now()
        return (end - self.started_at).total_seconds()

    def display_box(self) -> str:
        """Get box display for status views."""
        lines = [f"┌─ BLOCK: {self.name} {'─' * (60 - len(self.name))}┐"]

        for thread_id in self.thread_order:
            thread = self.threads[thread_id]
            lines.append(f"│{thread.display_line():<69}│")

        lines.append("└" + "─" * 70 + "┘")
        return "\n".join(lines)

    def to_dict(self) -> dict:
        """Serialize Block to dictionary."""
        return {
            "id": self.id,
            "name": self.name,
            "purpose": self.purpose,
            "cluster_id": self.cluster_id,
            "status": self.status.value,
            "progress": self.progress(),
            "blocked_by": self.blocked_by,
            "threads": [t.to_dict() for t in self.threads.values()],
            "created_at": self.created_at.isoformat(),
            "started_at": self.started_at.isoformat() if self.started_at else None,
            "completed_at": self.completed_at.isoformat() if self.completed_at else None,
        }

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