"""Service for managing PTY terminal sessions."""

import asyncio
import fcntl
import logging
import os
import pty
import signal
import struct
import termios

logger = logging.getLogger(__name__)


class TerminalSession:
    """A single PTY terminal session."""

    def __init__(self, master_fd: int, pid: int) -> None:
        self.master_fd = master_fd
        self.pid = pid
        self._closed = False
        self._reader_handle = None

    @property
    def closed(self) -> bool:
        return self._closed

    def write(self, data: bytes) -> None:
        """Write data to the PTY master (sends to the shell)."""
        if self._closed:
            return
        try:
            os.write(self.master_fd, data)
        except OSError:
            self._closed = True

    def resize(self, cols: int, rows: int) -> None:
        """Resize the PTY to the given dimensions."""
        if self._closed:
            return
        try:
            winsize = struct.pack("HHHH", rows, cols, 0, 0)
            fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
        except OSError:
            pass

    async def read(self) -> bytes | None:
        """Read available data from the PTY master. Returns None on EOF/close."""
        if self._closed:
            return None

        loop = asyncio.get_event_loop()
        future: asyncio.Future[bytes | None] = loop.create_future()

        def _on_readable() -> None:
            try:
                loop.remove_reader(self.master_fd)
            except (ValueError, OSError):
                pass
            try:
                data = os.read(self.master_fd, 4096)
                if not future.done():
                    future.set_result(data if data else None)
            except OSError:
                if not future.done():
                    future.set_result(None)

        try:
            loop.add_reader(self.master_fd, _on_readable)
        except (ValueError, OSError):
            return None

        return await future

    def close(self) -> None:
        """Close the PTY session and terminate the subprocess."""
        if self._closed:
            return
        self._closed = True

        # Remove any event loop readers
        try:
            loop = asyncio.get_event_loop()
            loop.remove_reader(self.master_fd)
        except (RuntimeError, ValueError, OSError):
            pass

        # Terminate the process
        try:
            os.killpg(os.getpgid(self.pid), signal.SIGTERM)
        except (OSError, ProcessLookupError):
            pass

        # Close file descriptor
        try:
            os.close(self.master_fd)
        except OSError:
            pass

        # Reap zombie
        try:
            os.waitpid(self.pid, os.WNOHANG)
        except (ChildProcessError, OSError):
            pass

        logger.info("Terminal session closed (pid=%d)", self.pid)


class TerminalService:
    """Manages PTY terminal sessions."""

    def __init__(self) -> None:
        self._session: TerminalSession | None = None

    @property
    def session(self) -> TerminalSession | None:
        return self._session

    @property
    def has_active_session(self) -> bool:
        return self._session is not None and not self._session.closed

    def create_session(self, cols: int = 80, rows: int = 24, cwd: str | None = None) -> TerminalSession:
        """Create a new PTY session running /bin/bash."""
        # Close existing session if any
        if self._session and not self._session.closed:
            self._session.close()

        master_fd, slave_fd = pty.openpty()

        # Set initial size
        winsize = struct.pack("HHHH", rows, cols, 0, 0)
        fcntl.ioctl(master_fd, termios.TIOCSWINSZ, winsize)

        pid = os.fork()
        if pid == 0:
            # Child process — become session leader and exec bash
            os.close(master_fd)
            os.setsid()

            fcntl.ioctl(slave_fd, termios.TIOCSCTTY, 0)
            os.dup2(slave_fd, 0)
            os.dup2(slave_fd, 1)
            os.dup2(slave_fd, 2)
            if slave_fd > 2:
                os.close(slave_fd)

            if cwd:
                try:
                    os.chdir(cwd)
                except OSError:
                    pass

            os.environ["TERM"] = "xterm-256color"
            os.execvp("/bin/bash", ["/bin/bash", "--norc", "--noprofile"])
        else:
            # Parent process
            os.close(slave_fd)

            # Make master non-blocking
            flags = fcntl.fcntl(master_fd, fcntl.F_GETFL)
            fcntl.fcntl(master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)

            self._session = TerminalSession(master_fd=master_fd, pid=pid)
            logger.info("Terminal session created (pid=%d, cols=%d, rows=%d)", pid, cols, rows)
            return self._session

    def close_session(self) -> None:
        """Close the active terminal session."""
        if self._session:
            self._session.close()
            self._session = None
