import os
import json
import struct
import signal
import select
import threading


def _pty_set_winsize(fd, rows, cols):
    import fcntl
    import termios

    winsz = struct.pack("HHHH", int(rows), int(cols), 0, 0)
    fcntl.ioctl(fd, termios.TIOCSWINSZ, winsz)


def terminal_session(ws):
    """PTY + bash, обмен через WebSocket (как ttyd, но на том же порту что и Flask)."""
    import pty

    pid, master_fd = pty.fork()
    if pid == 0:
        os.environ.setdefault("TERM", "xterm-256color")
        os.environ.setdefault("SHELL", "/bin/bash")
        try:
            os.execvp("/bin/bash", ["/bin/bash"])
        except OSError:
            os.execvp("/bin/sh", ["/bin/sh"])
        os._exit(127)

    relay_done = threading.Event()
    ws_lock = threading.Lock()

    def relay_out():
        try:
            while not relay_done.is_set():
                r, _, _ = select.select([master_fd], [], [], 0.25)
                if master_fd not in r:
                    continue
                try:
                    chunk = os.read(master_fd, 65536)
                except OSError:
                    break
                if not chunk:
                    break
                try:
                    with ws_lock:
                        ws.send(chunk)
                except Exception:
                    break
        finally:
            relay_done.set()

    t = threading.Thread(target=relay_out, daemon=True)
    t.start()

    try:
        while True:
            msg = ws.receive()
            if msg is None:
                break
            if isinstance(msg, str) and msg.startswith("{"):
                try:
                    j = json.loads(msg)
                    if j.get("type") == "resize":
                        r, c = int(j.get("rows") or 24), int(j.get("cols") or 80)
                        if r > 0 and c > 0:
                            _pty_set_winsize(master_fd, r, c)
                        continue
                except (json.JSONDecodeError, TypeError, ValueError, KeyError):
                    pass
            if isinstance(msg, str):
                msg = msg.encode("utf-8", errors="replace")
            try:
                os.write(master_fd, msg)
            except OSError:
                break
    finally:
        relay_done.set()
        try:
            os.close(master_fd)
        except OSError:
            pass
        try:
            os.kill(pid, signal.SIGTERM)
        except ProcessLookupError:
            pass
        try:
            os.waitpid(pid, 0)
        except ChildProcessError:
            pass
        t.join(timeout=1.5)
