"""Builtin command executor"""

from abc import ABC, abstractmethod
from typing import AsyncIterator, Optional

from diona.project.cli.core.models import ParsedInput, ExecutionResult, StreamChunk
from diona.project.cli.executor import BaseExecutor


class BuiltinExecutor(BaseExecutor):
    """Executor for builtin commands"""

    _instance: Optional["BuiltinExecutor"] = None

    def __new__(cls) -> "BuiltinExecutor":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return
        self._initialized = True

    def get_builtin_commands(self):
        """Get all builtin commands"""
        from diona.project.cli.register import get_builtin_registry
        registry = get_builtin_registry()
        return registry.get_all_commands()

    def can_execute(self, parsed_input: ParsedInput) -> bool:
        return parsed_input.input_type.value == "builtin"

    def execute(self, parsed_input: ParsedInput) -> ExecutionResult:
        from io import StringIO
        import sys
        from diona.project.cli.register import get_builtin_registry
        from diona.project.cli.core.result_storage import get_result_storage

        stdout_capture = StringIO()
        stderr_capture = StringIO()

        old_stdout = sys.stdout
        old_stderr = sys.stderr

        try:
            sys.stdout = stdout_capture
            sys.stderr = stderr_capture

            registry = get_builtin_registry()
            cmd = registry.get_command(parsed_input.command_name)
            if cmd:
                return_value = cmd.execute(parsed_input.args)
                
                sys.stdout = old_stdout
                sys.stderr = old_stderr

                stdout_value = stdout_capture.getvalue()
                stderr_value = stderr_capture.getvalue()
                
                # Store result
                storage = get_result_storage()
                storage.set_result(return_value, stdout_value, stderr_value)

                return ExecutionResult(
                    success=True,
                    return_code=0,
                    stdout=stdout_value,
                    stderr=stderr_value
                )
            else:
                sys.stdout = old_stdout
                sys.stderr = old_stderr
                
                error_msg = f"Command not found: {parsed_input.command_name}"
                # Store result
                storage = get_result_storage()
                storage.set_result(None, "", error_msg)
                return ExecutionResult(
                    success=False,
                    return_code=-1,
                    stderr=error_msg
                )
        except Exception as e:
            sys.stdout = old_stdout
            sys.stderr = old_stderr
            error_msg = str(e)
            # Store result
            storage = get_result_storage()
            storage.set_result(None, "", error_msg)
            return ExecutionResult(
                success=False,
                return_code=-1,
                error_message=error_msg
            )

    async def execute_stream(self, parsed_input: ParsedInput) -> AsyncIterator[StreamChunk]:
        from diona.project.cli.register import get_builtin_registry
        registry = get_builtin_registry()
        cmd = registry.get_command(parsed_input.command_name)

        if cmd is None:
            yield StreamChunk(
                content=f"Command not found: {parsed_input.command_name}",
                is_error=True,
                is_final=True,
                return_code=-1
            )
            return

        if hasattr(cmd, 'execute_stream') and callable(getattr(cmd, 'execute_stream')):
            try:
                result = cmd.execute_stream(parsed_input.args)
                if result is not None:
                    async for chunk in result:
                        yield StreamChunk(content=chunk)
            except Exception as e:
                yield StreamChunk(
                    content=str(e),
                    is_error=True,
                    is_final=True,
                    return_code=-1
                )
        else:
            result = self.execute(parsed_input)
            if result.success:
                if result.stdout:
                    for line in result.stdout.splitlines(keepends=True):
                        yield StreamChunk(content=line)
            else:
                yield StreamChunk(
                    content=result.error_message or "Unknown error",
                    is_error=True,
                    is_final=True,
                    return_code=result.return_code
                )

        yield StreamChunk(content="", is_final=True, return_code=0)
