import uuid
import json
from multiprocessing import Queue
import queue
import time
from hiveos.agent_interface import AgentInterface
from hiveos.runtime.states import AgentStatus

class SimTerminalAgent(AgentInterface):
    def __init__(self, agent_id=None, name=None, capabilities=None):
        super().__init__()
        self.agent_id = agent_id or f"sim-terminal-{str(uuid.uuid4())[:8]}"
        self.name = str(name or f"SimTerm-{str(uuid.uuid4())[:4]}")
        self.zone = None
        self.status = AgentStatus.IDLE
        self.capabilities = ["terminal", "display"]
        self._available = True
        self.current_chunk = None
        self.chunk_progress = 0
        self.required_ticks = 0
        self.waiting_on_input = False

        self.input_queue = Queue()
        self.latest_output = ""
        self.latest_input = None
        self.wait_started_mono = None
        self.wait_timeout_seconds = None
        self._wait_token = None
        self._core = None

    def bind_core(self, core):
        self._core = core

    def get_status(self):
        return self.status

    def report_capabilities(self):
        return self.capabilities

    def execute_chunk(self, chunk, payload=None):
        # Backwards compatibility: wrap legacy string payload in dict
        if isinstance(payload, str):
            payload = {"intent": "render_text", "parameters": {"text": payload}}

        intent = payload.get("intent", "")
        self.current_intent = intent
        params = payload.get("parameters", {})

        if intent == "render_text":
            text = params.get("text", "")
            self.latest_output = text                     
            self.execution_started = True
            self.ready_to_execute = False
        
        elif intent == "wait_for_input":
            if not self.waiting_on_input:
                # Trigger prompt as soon as the chunk is assigned and ready
                prompt = params.get("prompt", "Enter input:")
                self.latest_output = prompt
                self.waiting_on_input = True
                self.wait_started_mono = time.monotonic()
                self.wait_timeout_seconds = self._resolve_wait_timeout_seconds(chunk, params)
                self._wait_token = f"{chunk.chunk_id}:{time.time_ns()}"
                wait_token = self._wait_token

                def wait_and_publish():
                    while True:
                        if not self.waiting_on_input or self.current_chunk != chunk.chunk_id:
                            break
                        if self._wait_token != wait_token:
                            break
                        try:
                            user_input = self.input_queue.get(timeout=0.2)
                        except queue.Empty:
                            continue
                        self.latest_input = user_input
                        if self.message_bus is not None:
                            self.message_bus.publish("terminal_input", user_input)
                        self.waiting_on_input = False
                        self.wait_started_mono = None
                        self.wait_timeout_seconds = None
                        self.report_completion(chunk)
                        break

                import threading
                threading.Thread(target=wait_and_publish, daemon=True).start()
        elif intent == "terminal_action":
            self._execute_terminal_action(chunk, payload)
        else:
            self.latest_output = f"Unknown intent: {intent}"

    def tick(self):
        super().tick()
        if not self.waiting_on_input or self.wait_started_mono is None:
            return
        if not self.wait_timeout_seconds or self.wait_timeout_seconds <= 0:
            return
        if (time.monotonic() - self.wait_started_mono) < self.wait_timeout_seconds:
            return

        self.waiting_on_input = False
        self.wait_started_mono = None
        self.wait_timeout_seconds = None
        timeout_msg = "terminal_input_timeout"
        self.latest_input = timeout_msg
        if self.message_bus is not None:
            self.message_bus.publish("terminal_input", timeout_msg)
        if self.chunk_obj is not None:
            self.report_completion(self.chunk_obj)
    
    def health_check(self):
        return True

    def enter_cooldown(self, reason=""):
        self.status = AgentStatus.COOLDOWN
        self.latest_output = f"[COOLDOWN] {reason}"

    def is_available(self):
        return self._available and self.status == AgentStatus.IDLE
        
    def receive_input(self, user_input: str):
        if self.waiting_on_input:
            self.input_queue.put(user_input)

    def get_summary(self):
            return super().get_summary() | {
                "latest_output": self.latest_output,
                "latest_input": self.latest_input,
                "current_intent": getattr(self, "current_intent", None),
                "waiting_on_input": self.waiting_on_input,
                "wait_timeout_seconds": self.wait_timeout_seconds,
            }

    def _resolve_wait_timeout_seconds(self, chunk, params):
        raw = params.get("timeout_seconds")
        if raw is not None:
            try:
                return max(1.0, float(raw))
            except Exception:
                pass

        if chunk.timing == "seconds":
            return max(1.0, float(chunk.ttl))
        if chunk.timing == "ms":
            return max(0.25, float(chunk.ttl) / 1000.0)
        # Tick-based fallback: 1 tick ~= 0.1s with floor/ceiling guard.
        return max(2.0, min(300.0, float(chunk.ttl) * 0.1))

    def _execute_terminal_action(self, chunk, payload):
        if self._core is None:
            self.latest_output = "terminal_action_failed:missing_core_binding"
            self._mark_chunk_failed(chunk)
            return
        try:
            tool_id = str(payload.get("tool_id") or "")
            action = str(payload.get("action") or "")
            args = dict(payload.get("args") or {})
            trace_context = dict(payload.get("trace_context") or {})
            trace_context.setdefault("agent_id", self.agent_id)
            trace_context.setdefault("task_id", chunk.task_id)
            trace_context.setdefault("chunk_id", chunk.chunk_id)
            trace_context.setdefault("zone_id", self.zone)

            if tool_id == "terminal.session" and action == "open":
                result = self._core.terminal_service.open_target(
                    {
                        "target_id": args.get("target_id"),
                        "target_type": args.get("target_type"),
                        "session_id": self._core.session_id,
                        "agent_id": self.agent_id,
                        "transport": args.get("transport"),
                        "meta": args.get("meta"),
                    },
                    trace_context=trace_context,
                )
            elif tool_id == "terminal.transform" and action == "apply":
                result = self._core.terminal_service.set_transform(
                    target_id=str(args.get("target_id") or ""),
                    module=str(args.get("module") or ""),
                    config=args.get("config") or {},
                    trace_context=trace_context,
                )
            elif tool_id == "terminal.exec" and action == "run":
                result = self._core.terminal_service.run_command(
                    target_id=str(args.get("target_id") or ""),
                    command=str(args.get("command") or ""),
                    cwd=args.get("cwd"),
                    env=args.get("env"),
                    timeout_sec=int(args.get("timeout_sec") or 30),
                    trace_context=trace_context,
                )
            else:
                raise ValueError(f"unsupported_terminal_action:{tool_id}:{action}")

            self.latest_output = f"terminal_action_applied:{json.dumps({'tool_id': tool_id, 'action': action})}"
            self.report_completion(chunk)
            return result
        except Exception as exc:
            self.latest_output = f"terminal_action_failed:{exc}"
            self._mark_chunk_failed(chunk)
            return None

    def _mark_chunk_failed(self, chunk):
        self.has_executed = True
        self.status = AgentStatus.IDLE
        self.current_chunk = None
        self.chunk_progress = 0
        self.required_ticks = 0
        self.execution_started = False
        self.ready_to_execute = False
        if chunk is not None:
            chunk.mark_stale()
