"""Core executor for handling user input and command execution"""

import asyncio
import sys
from typing import AsyncIterator, Optional

from diona.project.cli.core.parser import get_input_parser
from diona.project.cli.core.output import get_output_engine
from diona.project.cli.core.mode import ModeManager, get_mode_manager, ModeConstants
from diona.project.cli.executor import (
    get_executor_registry,
    BuiltinExecutor,
    RegisteredExecutor,
    SystemExecutor,
    AgentExecutor,
)
from diona.project.cli.core.models import ParsedInput, StreamChunk, InputType


class CLIConstants:
    """Constants for CLI"""

    MODE_PREFIX = "$"
    LOCAL_PREFIX = "/local "
    
    MODE_AGENT = "agent"
    MODE_WEB = "web"
    MODE_CLIENT = "client"
    MODE_EXIT = "exit"


class ExecutorCore:
    """Core executor for handling user input and command execution"""

    _instance: Optional["ExecutorCore"] = None

    def __new__(cls) -> "ExecutorCore":
        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._parser = get_input_parser()
        self._output = get_output_engine()
        self._executor_registry = get_executor_registry()
        self._mode_manager = get_mode_manager()
        self._register_executors()
        self._register_modes()
        self._initialized = True

    def _register_executors(self):
        """Register all executors"""
        self._executor_registry.register(BuiltinExecutor())
        self._executor_registry.register(RegisteredExecutor())
        self._executor_registry.register(SystemExecutor())
        self._executor_registry.register(AgentExecutor())

    def _register_modes(self):
        """Register all modes"""
        from diona.project.cli.core.modes.client_mode import ClientModeHandler
        from diona.project.cli.core.modes.agent_mode import AgentModeHandler
        from diona.project.cli.core.modes.web_mode import WebModeHandler

        client_mode = ClientModeHandler()
        agent_mode = AgentModeHandler()
        web_mode = WebModeHandler()

        self._mode_manager.register_mode(client_mode)
        self._mode_manager.register_mode(agent_mode)
        self._mode_manager.register_mode(web_mode)

    def set_mode(self, mode: str) -> bool:
        """Set the current mode
        
        Args:
            mode: Mode name
            
        Returns:
            True if mode was set successfully
        """
        return self._mode_manager.set_mode(mode)

    def get_current_mode(self) -> str:
        """Get the current mode name"""
        if self._mode_manager.current_mode:
            return self._mode_manager.current_mode.name
        return ModeConstants.MODE_CLIENT

    def is_default_mode(self) -> bool:
        """Check if in default mode (client mode)"""
        return self._mode_manager.is_default_mode

    def execute_sync(self, user_input: str):
        """Execute user input synchronously
        
        Args:
            user_input: User input string
        """
        try:
            loop = asyncio.get_running_loop()
            loop.create_task(self._execute_and_print(user_input))
        except RuntimeError:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            try:
                loop.run_until_complete(self._execute_and_print(user_input))
            finally:
                loop.close()

    async def _execute_and_print(self, user_input: str):
        """Execute and print results"""
        async for chunk in self.execute(user_input):
            if chunk.content:
                print(chunk.content, end="", flush=True)
            if chunk.is_final:
                print()

    async def execute(self, user_input: str) -> AsyncIterator[StreamChunk]:
        """Execute user input and yield streaming results
        
        Args:
            user_input: User input string
            
        Yields:
            StreamChunk results
        """
        if user_input.startswith(CLIConstants.LOCAL_PREFIX):
            local_input = user_input[len(CLIConstants.LOCAL_PREFIX):].strip()
            async for chunk in self._execute_local(local_input):
                yield chunk
            return

        current_mode = self.get_current_mode()

        if current_mode == CLIConstants.MODE_WEB:
            async for chunk in self._handle_web_mode(user_input):
                yield chunk
            return

        if user_input.startswith(CLIConstants.MODE_PREFIX):
            command = user_input[1:].strip()
            
            if command == CLIConstants.MODE_EXIT:
                self._mode_manager.set_mode(CLIConstants.MODE_CLIENT)
                yield StreamChunk(content="Exited to client mode.\n", is_final=True)
                return
            
            if command in (CLIConstants.MODE_AGENT, CLIConstants.MODE_WEB, CLIConstants.MODE_CLIENT):
                self._mode_manager.set_mode(command)
                yield StreamChunk(content=f"Switched to {command} mode.\n", is_final=True)
                return
            
            yield StreamChunk(content=f"Unknown mode: {command}\n", is_error=True, is_final=True)
            return

        if current_mode == CLIConstants.MODE_AGENT:
            if user_input.startswith("$exit"):
                self._mode_manager.set_mode(CLIConstants.MODE_CLIENT)
                yield StreamChunk(content="Exited to client mode.\n", is_final=True)
                return
            
            if user_input.startswith("$"):
                command = user_input[1:].strip()
                if command in (CLIConstants.MODE_AGENT, CLIConstants.MODE_WEB, CLIConstants.MODE_CLIENT):
                    self._mode_manager.set_mode(command)
                    yield StreamChunk(content=f"Switched to {command} mode.\n", is_final=True)
                    return

            if user_input.startswith("/"):
                parsed = self._parser.parse(user_input)
                stream = self._executor_registry.execute_stream(parsed)
                async for chunk in stream:
                    yield chunk
                return

            parsed = ParsedInput(
                raw_input=user_input,
                input_type=InputType.AGENT,
                command_name="agent",
                args=[]
            )
            stream = self._executor_registry.execute_stream(parsed)
            async for chunk in stream:
                yield chunk
            return

        parsed = self._parser.parse(user_input)
        stream = self._executor_registry.execute_stream(parsed)
        async for chunk in stream:
            yield chunk

    async def _handle_web_mode(self, user_input: str) -> AsyncIterator[StreamChunk]:
        """Handle web mode - top level mode, only $exit exits
        
        Args:
            user_input: User input string
            
        Yields:
            StreamChunk results
        """
        import os
        from diona.project.webserver.constants import DEFAULT_WEB_ADDR, DEFAULT_WEB_PORT, ENV_WEB_ADDR, ENV_WEB_PORT
        from diona.project.encrypt import get_crypto_manager

        if user_input == "$exit":
            self._mode_manager.set_mode(CLIConstants.MODE_CLIENT)
            yield StreamChunk(content="Exited to client mode.\n", is_final=True)
            return

        web_addr = os.environ.get(ENV_WEB_ADDR, DEFAULT_WEB_ADDR)
        web_port = os.environ.get(ENV_WEB_PORT, str(DEFAULT_WEB_PORT))

        try:
            web_port = int(web_port)
        except ValueError:
            web_port = DEFAULT_WEB_PORT

        url = f"http://{web_addr}:{web_port}/execute/stream"

        try:
            import aiohttp

            crypto = get_crypto_manager()
            encrypted_command = crypto.encrypt(user_input)

            async with aiohttp.ClientSession() as session:
                async with session.post(
                    url,
                    json={"command": encrypted_command},
                    timeout=aiohttp.ClientTimeout(total=60)
                ) as response:
                    if response.status == 200:
                        async for line in response.content:
                            decoded = line.decode("utf-8").strip()
                            if decoded.startswith("data: "):
                                data = decoded[6:]
                                decrypted = crypto.decrypt(data)
                                yield StreamChunk(content=decrypted)
                            elif decoded.startswith("error: "):
                                data = decoded[7:]
                                decrypted = crypto.decrypt(data)
                                yield StreamChunk(content=f"[ERROR] {decrypted}", is_error=True)
                            elif decoded.startswith("done:"):
                                yield StreamChunk(content="", is_final=True)
                    else:
                        yield StreamChunk(content=f"[ERROR] Webserver returned status {response.status}", is_error=True, is_final=True)

        except ImportError:
            yield StreamChunk(content="[ERROR] aiohttp library is required for web mode", is_error=True, is_final=True)
        except ConnectionRefusedError:
            yield StreamChunk(content=f"[ERROR] Cannot connect to webserver at http://{web_addr}:{web_port}", is_error=True, is_final=True)
        except Exception as e:
            yield StreamChunk(content=f"[ERROR] {str(e)}", is_error=True, is_final=True)

    async def _execute_local(self, local_input: str) -> AsyncIterator[StreamChunk]:
        """Execute command locally regardless of current mode
        
        Args:
            local_input: Local command to execute
            
        Yields:
            StreamChunk results
        """
        parsed = self._parser.parse(local_input)
        stream = self._executor_registry.execute_stream(parsed)
        async for chunk in stream:
            yield chunk


def get_executor_core() -> ExecutorCore:
    """Get the singleton executor core instance
    
    Returns:
        ExecutorCore instance
    """
    return ExecutorCore()
