import asyncio
import json
import os
import pty
import shutil
import sys
import tty
import termios
import time
import threading

import tornado.iostream
from tornado.ioloop import IOLoop
from tornado.websocket import websocket_connect

ioloop = tornado.ioloop.IOLoop.instance()


SSH_LOGIN = "root"
SSH_PASSWORD = "algorave"

SCREEN_TO_SCREEN_0_SEQ = b"ls -l\r\x1bOC" + b"\x010"  # ^A 0


async def send_stdin_to_ws_task(ws, on_finish_cb):
    print("mangling terminal")
    try:
        fn = os.dup(sys.stdin.fileno())
        inp = tornado.iostream.PipeIOStream(fn)
        mode = termios.tcgetattr(sys.stdin.fileno())
        tty.setraw(fn)
        while True:
            try:
                print("reading stdin", end="\r\n")
                content = await inp.read_bytes(100, partial=True)
                print("read stdin", end="\r\n")
            #    content = await self.inp.read_bytes(100, partial=True)
            except tornado.iostream.StreamClosedError:
                print("Stdin closed", end="\r\n")
                # await self.finish()
                ioloop.add_callback(on_finish_cb)
                break
            print(f"stdin: {content}", end="\r\n")
            if content[0] == 3 or not content:  # CTRL-C
                print("Got a ^C", end="\r\n")
                ioloop.add_callback(on_finish_cb)
                break
            ioloop.add_callback(
                ws.write_message,
                json.dumps(
                    {
                        "client_command": "keystrokes",
                        "keystrokes": [int(x) for x in content],
                    }
                ),
            )
        print("no exc", end="\r\n")
    except asyncio.CancelledError:
        print("stdin read task cancelled", end="\r\n")
    except Exception as e:  # pylint: disable=broad-except
        print(f"Exception: {e}")
    finally:
        inp.close()
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, mode)
        print("finally")


async def run_ssh(host, port, login=SSH_LOGIN, password=SSH_PASSWORD):
    os.environ["SSHPASS"] = password
    ssh_cmd = [
        "ssh",
        "-o",
        "PreferredAuthentications=password",
        "-o",
        "PubkeyAuthentication=no",
        "-o",
        "StrictHostKeyChecking=no",  # Skip fingerpint warning.
        f"{login}@{host}",
        "-p",
        str(port),
    ]
    sshpass_cmd = [shutil.which("sshpass"), "-e"] + ssh_cmd
    args = sshpass_cmd
    print(" ".join(args))

    e = threading.Event()

    def stdin_read(fd):
        if not e.is_set():
            e.set()
            return SCREEN_TO_SCREEN_0_SEQ + os.read(fd, 1024)

        b = os.read(fd, 1024)
        return b

    def master_read(fd):
        b = os.read(fd, 1024)
        return b

    # Let Web UI connect to screen 0 first.
    time.sleep(3)
    res = pty.spawn(args, master_read=master_read, stdin_read=stdin_read)
    print(f"ssh returned {res}")


class Client:
    mode: str

    def __init__(self, url, timeout):
        self.url = url
        self.timeout = timeout
        self.ioloop = IOLoop.instance()
        self.ws = None

        self.send_stdin_task = None

    async def connect(self):
        print("trying to connect")
        try:
            self.ws = await websocket_connect(self.url)
        except Exception as e:  # pylint: disable=broad-except
            print(f"connection error: {str(e)}")
        else:
            print("connected")
            # await self.ws.write_message({'client': self.i})
            self.mode = "idle"
            self.ioloop.spawn_callback(self.run_idle)
            self.ioloop.spawn_callback(self.run)

    def finish_ws(self):
        if self.ws:
            self.ws.close()
            self.ws = None

    async def finish(self):
        if self.send_stdin_task:
            await self.stop_idle()

        self.finish_ws()
        self.ioloop.stop()

    async def run_idle(self):
        assert not self.send_stdin_task
        print("running idle, spawning task")
        self.send_stdin_task = asyncio.create_task(
            send_stdin_to_ws_task(self.ws, self.finish)
        )

    async def stop_idle(self):
        assert self.send_stdin_task
        self.send_stdin_task.cancel()
        await self.send_stdin_task
        self.send_stdin_task = None

    @staticmethod
    async def run_ssh(host, port):
        # Blocks ioloop
        await run_ssh(host, port)

    async def run(self):
        while True:
            msg = await self.ws.read_message()
            if msg is None:
                print("server left, terminating", end="\r\n")
                self.ioloop.add_callback(self.finish)
                return

            msg = json.loads(msg)
            print(f"got msg: {msg}", end="\r\n")
            if "mode" not in msg:
                continue
            if msg["mode"] == "ssh":
                host, port = msg["ssh"]["host"], msg["ssh"]["port"]
                print(f"Connecting to ssh {host}:{port}...", end="\r\n")
                await self.stop_idle()
                await self.run_ssh(host, port)
                print("restarting idle task")
                self.finish_ws()
                await self.connect()
                break

[((86, 8, 86, 25), 'threading.Event', 'threading.Event', ({}, {}), '()', False, 'import threading\n'), ((101, 4, 101, 17), 'time.sleep', 'time.sleep', ({(101, 15, 101, 16): '(3)'}, {}), '(3)', False, 'import time\n'), ((102, 10, 102, 73), 'pty.spawn', 'pty.spawn', (), '', False, 'import pty\n'), ((31, 8, 31, 22), 'tty.setraw', 'tty.setraw', ({(31, 19, 31, 21): 'fn'}, {}), '(fn)', False, 'import tty\n'), ((64, 8, 64, 61), 'termios.tcsetattr', 'termios.tcsetattr', ({(64, 26, 64, 35): 'sys.stdin', (64, 37, 64, 54): 'termios.TCSADRAIN', (64, 56, 64, 60): 'mode'}, {}), '(sys.stdin, termios.TCSADRAIN, mode)', False, 'import termios\n'), ((93, 12, 93, 29), 'os.read', 'os.read', ({(93, 20, 93, 22): 'fd', (93, 24, 93, 28): '1024'}, {}), '(fd, 1024)', False, 'import os\n'), ((97, 12, 97, 29), 'os.read', 'os.read', ({(97, 20, 97, 22): 'fd', (97, 24, 97, 28): '1024'}, {}), '(fd, 1024)', False, 'import os\n'), ((112, 22, 112, 39), 'tornado.ioloop.IOLoop.instance', 'IOLoop.instance', ({}, {}), '()', False, 'from tornado.ioloop import IOLoop\n'), ((28, 20, 28, 38), 'sys.stdin.fileno', 'sys.stdin.fileno', ({}, {}), '()', False, 'import sys\n'), ((30, 33, 30, 51), 'sys.stdin.fileno', 'sys.stdin.fileno', ({}, {}), '()', False, 'import sys\n'), ((82, 19, 82, 42), 'shutil.which', 'shutil.which', ({(82, 32, 82, 41): '"""sshpass"""'}, {}), "('sshpass')", False, 'import shutil\n'), ((168, 18, 168, 33), 'json.loads', 'json.loads', ({(168, 29, 168, 32): 'msg'}, {}), '(msg)', False, 'import json\n'), ((91, 44, 91, 61), 'os.read', 'os.read', ({(91, 52, 91, 54): 'fd', (91, 56, 91, 60): '(1024)'}, {}), '(fd, 1024)', False, 'import os\n'), ((120, 28, 120, 55), 'tornado.websocket.websocket_connect', 'websocket_connect', ({(120, 46, 120, 54): 'self.url'}, {}), '(self.url)', False, 'from tornado.websocket import websocket_connect\n')]