"""ExecutePhaseService - Orchestrate phase/step execution.

Contract: CNT-T2-SVC-001
Traceability: UC-001, BR-001, BR-002, BR-003, BR-004, UX-001
"""

from collections.abc import Callable
from pathlib import Path

from laida_cli.domain.execution_result import ExecutionResult
from laida_cli.domain.execution_status import ExecutionStatus
from laida_cli.domain.phase_execution import PhaseExecution
from laida_cli.domain.phase_number import PhaseNumber
from laida_cli.domain.project_path import ProjectPath
from laida_cli.domain.step_number import StepNumber
from laida_cli.ports.claude_code_port import ClaudeCodeError, ClaudeCodePort
from laida_cli.services.detect_completion import DetectCompletionService
from laida_cli.services.detect_state import DetectStateService
from laida_cli.services.manage_context import ManageContextService


class ExecutePhaseService:
    """Service for orchestrating phase/step execution.

    Responsibilities:
    1. Create PhaseExecution aggregate
    2. Clear Claude context (BR-001)
    3. Load prompt from .laida/prompts/
    4. Invoke Claude Code
    5. Detect completion (BR-002)
    6. Verify output files
    7. Return ExecutionResult
    """

    def __init__(
        self,
        claude_port: ClaudeCodePort,
        context_service: ManageContextService | None = None,
        state_service: DetectStateService | None = None,
        completion_service: DetectCompletionService | None = None,
    ) -> None:
        """Initialize with Claude Code port and optional collaborators.

        Args:
            claude_port: Port for Claude Code interaction
            context_service: Service for context management (uses default if None)
            state_service: Service for state detection (uses default if None)
            completion_service: Service for completion detection (uses default if None)
        """
        self._claude_port = claude_port
        self._context_service = context_service or ManageContextService(claude_port)
        self._state_service = state_service or DetectStateService()
        self._completion_service = completion_service or DetectCompletionService()

    def execute(
        self,
        phase: int,
        step: int,
        project_path: str,
        skip_context_clear: bool = False,
    ) -> ExecutionResult:
        """Execute a specific phase/step.

        Args:
            phase: Phase number (0-4)
            step: Step number (0-3)
            project_path: Path to the LAIDA project
            skip_context_clear: Skip clearing context (for debugging)

        Returns:
            ExecutionResult with outcome
        """
        try:
            # Validate inputs
            phase_num = PhaseNumber.from_int(phase)
            step_num = StepNumber.from_int(step)
            proj_path = ProjectPath.from_string(project_path)

            # Load prompt
            prompt = self._load_prompt(proj_path.path, phase, step)
            if not prompt:
                return ExecutionResult.failure_result(
                    error_message=f"Prompt not found for Phase {phase} Step {step}"
                )

            # Clear context (BR-001)
            if not skip_context_clear:
                self._context_service.clear_context()

            # Create and execute
            execution = PhaseExecution(
                project_path=proj_path,
                phase=phase_num,
                step=step_num,
            )

            return execution.execute(self._claude_port, prompt)

        except ValueError as e:
            return ExecutionResult.failure_result(error_message=str(e))
        except ClaudeCodeError as e:
            return ExecutionResult.failure_result(error_message=str(e))

    def execute_streaming(
        self,
        phase: int,
        step: int,
        project_path: str,
        on_output: Callable[[str], None],
        skip_context_clear: bool = False,
    ) -> ExecutionResult:
        """Execute a phase/step with streaming output.

        Same as execute() but invokes on_output callback for each line
        of Claude Code output, enabling real-time UI feedback.

        Args:
            phase: Phase number (0-4)
            step: Step number (0-3)
            project_path: Path to the LAIDA project
            on_output: Callback invoked for each output line
            skip_context_clear: Skip clearing context (for debugging)

        Returns:
            ExecutionResult with outcome
        """
        try:
            # Validate inputs
            phase_num = PhaseNumber.from_int(phase)
            step_num = StepNumber.from_int(step)
            proj_path = ProjectPath.from_string(project_path)

            # Load prompt
            prompt = self._load_prompt(proj_path.path, phase, step)
            if not prompt:
                return ExecutionResult.failure_result(
                    error_message=f"Prompt not found for Phase {phase} Step {step}"
                )

            # Clear context (BR-001)
            if not skip_context_clear:
                self._context_service.clear_context()

            # Create execution aggregate for state tracking
            execution = PhaseExecution(
                project_path=proj_path,
                phase=phase_num,
                step=step_num,
            )

            # Transition to RUNNING
            execution._transition_to(ExecutionStatus.RUNNING)

            # Stream output, collecting all lines
            output_lines: list[str] = []
            for line in self._claude_port.stream_prompt(prompt, proj_path.path):
                output_lines.append(line)
                on_output(line)

            output = "".join(output_lines)

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

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

            execution._set_result(result)
            return result

        except ValueError as e:
            return ExecutionResult.failure_result(error_message=str(e))
        except ClaudeCodeError as e:
            return ExecutionResult.failure_result(error_message=str(e))

    def _load_prompt(self, project_path: Path, phase: int, step: int) -> str | None:
        """Load the prompt for a specific phase/step.

        Args:
            project_path: Path to the LAIDA project
            phase: Phase number
            step: Step number

        Returns:
            Prompt content or None if not found
        """
        prompts_dir = project_path / ".laida" / "prompts" / f"phase{phase}"

        if not prompts_dir.exists():
            return None

        # Find the step prompt file
        step_name = self._state_service.get_step_name(phase, step)
        prompt_file = prompts_dir / f"{step_name}.yaml"

        if prompt_file.exists():
            return prompt_file.read_text()

        # Try alternative naming
        for pattern in [f"step{step}_*.yaml", f"step{step}.yaml"]:
            matches = list(prompts_dir.glob(pattern))
            if matches:
                return matches[0].read_text()

        return None

    def get_current_state(self, project_path: str) -> str:
        """Get current project state as string.

        Args:
            project_path: Path to the LAIDA project

        Returns:
            Human-readable state description
        """
        path = Path(project_path).resolve()
        state = self._state_service.detect(path)
        return str(state)
