"""PhaseExecution - Aggregate root for phase execution lifecycle.

Contract: CNT-T0-DOM-006
Traceability: UC-001, UC-002, UC-003, INV-010, INV-011, INV-012, BR-001, BR-002, BR-003, BR-004
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from laida_cli.domain.execution_result import ExecutionResult
from laida_cli.domain.execution_status import ExecutionStatus
from laida_cli.domain.phase_number import PhaseNumber
from laida_cli.domain.project_path import ProjectPath
from laida_cli.domain.step_number import StepNumber

if TYPE_CHECKING:
    from laida_cli.ports.claude_code_port import ClaudeCodePort


class IllegalStateError(Exception):
    """Raised when an operation is attempted in an invalid state."""


@dataclass
class PhaseExecution:
    """Aggregate root managing phase execution lifecycle.

    Invariants:
    - INV-010: status transitions follow state machine
    - INV-011: execute() can only be called when status == PENDING
    - INV-012: result can only be set once
    """

    project_path: ProjectPath
    phase: PhaseNumber
    step: StepNumber
    status: ExecutionStatus = field(default=ExecutionStatus.PENDING)
    result: ExecutionResult | None = field(default=None)

    def _transition_to(self, new_status: ExecutionStatus) -> None:
        """Transition to a new status with validation.

        Args:
            new_status: Target status

        Raises:
            IllegalStateError: If transition is not valid
        """
        if not self.status.can_transition_to(new_status):
            raise IllegalStateError(
                f"Cannot transition from {self.status} to {new_status}"
            )
        object.__setattr__(self, "status", new_status)

    def execute(self, claude_port: ClaudeCodePort, prompt: str) -> ExecutionResult:
        """Execute the phase/step.

        Args:
            claude_port: Port for Claude Code interaction
            prompt: The prompt to send to Claude Code

        Returns:
            ExecutionResult with outcome

        Raises:
            IllegalStateError: If not in PENDING status
        """
        if self.status != ExecutionStatus.PENDING:
            raise IllegalStateError(
                f"Cannot execute in status {self.status}. Must be PENDING."
            )

        self._transition_to(ExecutionStatus.RUNNING)

        try:
            # Send prompt to Claude Code
            output = claude_port.send_prompt(prompt, self.project_path.path)

            # Check for completion
            expected_dir = (
                self.project_path.outputs_dir()
                / f"phase{self.phase.value}"
                / f"step{self.step.value}_*"
            )
            files_created = claude_port.check_completion([str(expected_dir)])

            if files_created:
                result = ExecutionResult.success_result(
                    output_files=[output],
                    tokens_used=len(output) // 4,  # Rough estimate
                )
            else:
                result = ExecutionResult.failure_result(
                    error_message="Expected output files not found",
                    tokens_used=len(output) // 4,
                )

            self._set_result(result)
            return result

        except Exception as e:
            result = ExecutionResult.failure_result(error_message=str(e))
            self._set_result(result)
            return result

    def _set_result(self, result: ExecutionResult) -> None:
        """Set the execution result.

        Args:
            result: The execution result

        Raises:
            IllegalStateError: If result is already set
        """
        if self.result is not None:
            raise IllegalStateError("Result can only be set once")

        object.__setattr__(self, "result", result)

        if result.success:
            self._transition_to(ExecutionStatus.COMPLETED)
        else:
            self._transition_to(ExecutionStatus.FAILED)

    def mark_failed(self, error: str) -> None:
        """Mark execution as failed.

        Args:
            error: Error message

        Raises:
            IllegalStateError: If not in RUNNING status
        """
        if self.status != ExecutionStatus.RUNNING:
            raise IllegalStateError(
                f"Cannot mark failed in status {self.status}. Must be RUNNING."
            )

        result = ExecutionResult.failure_result(error_message=error)
        self._set_result(result)

    def clear_context(self, claude_port: ClaudeCodePort) -> bool:
        """Clear Claude Code context.

        Args:
            claude_port: Port for Claude Code interaction

        Returns:
            True if context was cleared successfully
        """
        return claude_port.send_command("/clear")

    def __str__(self) -> str:
        """Return string representation."""
        return f"PhaseExecution(phase={self.phase}, step={self.step}, status={self.status})"
