"""IPC lokal (127.0.0.1) antara service agent (SYSTEM, sesi 0) dan helper (sesi pengguna).

Service tidak dapat menampilkan UI atau menangkap layar di sesi pengguna, sehingga hal-hal itu dikerjakan helper.
Pesan: JSON per baris. Helper wajib mengirim 'hello' berisi kunci IPC (berkas ipc.key).
"""
from __future__ import annotations

import asyncio
import hmac
import json
import logging
import secrets
from typing import Awaitable, Callable

from config import IPC_PORT, agent_home

log = logging.getLogger("agent.ipc")


def ipc_key() -> str:
    f = agent_home() / "ipc.key"
    if not f.exists():
        f.write_text(secrets.token_hex(16))
    return f.read_text().strip()


class IpcServer:
    def __init__(self, on_message: Callable[[dict], Awaitable[None]], port: int = IPC_PORT):
        self.on_message, self.port = on_message, port
        self.writer: asyncio.StreamWriter | None = None
        self._server = None

    @property
    def connected(self) -> bool:
        return self.writer is not None and not self.writer.is_closing()

    async def start(self) -> None:
        ipc_key()  # buat kunci SEKARANG agar helper bisa membacanya sebelum mengirim 'hello'
        self._server = await asyncio.start_server(self._handle, "127.0.0.1", self.port)

    async def _handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
        try:
            line = await asyncio.wait_for(reader.readline(), 5)
            hello = json.loads(line or b"{}")
            if hello.get("type") != "hello" or not hmac.compare_digest(str(hello.get("key", "")), ipc_key()):
                writer.close()
                return
            if self.writer and not self.writer.is_closing():
                self.writer.close()
            self.writer = writer
            await self.on_message(hello)
            while True:
                line = await reader.readline()
                if not line:
                    break
                if len(line) > 6_000_000:
                    continue
                await self.on_message(json.loads(line))
        except (asyncio.TimeoutError, json.JSONDecodeError, ConnectionError):
            pass
        finally:
            if self.writer is writer:
                self.writer = None
                await self.on_message({"type": "helper_gone"})
            writer.close()

    async def send(self, msg: dict) -> bool:
        if not self.connected:
            return False
        try:
            self.writer.write((json.dumps(msg) + "\n").encode())
            await self.writer.drain()
            return True
        except ConnectionError:
            return False

    async def stop(self) -> None:
        if self._server:
            self._server.close()
