from __future__ import annotations

import argparse
import select
import socket
import sys
import threading
import time

import paramiko


def _bridge_channels(chan: paramiko.Channel, upstream: socket.socket) -> None:
    try:
        while True:
            readers, _, _ = select.select([chan, upstream], [], [], 1.0)
            if chan in readers:
                data = chan.recv(4096)
                if not data:
                    break
                upstream.sendall(data)
            if upstream in readers:
                data = upstream.recv(4096)
                if not data:
                    break
                chan.sendall(data)
    finally:
        try:
            chan.close()
        except Exception:
            pass
        try:
            upstream.close()
        except Exception:
            pass


def _accept_channels(transport: paramiko.Transport, local_host: str, local_port: int) -> None:
    while transport.is_active():
        chan = transport.accept(1000)
        if chan is None:
            continue
        try:
            upstream = socket.create_connection((local_host, local_port), timeout=10)
        except OSError:
            chan.close()
            continue
        threading.Thread(target=_bridge_channels, args=(chan, upstream), daemon=True).start()


def main() -> int:
    parser = argparse.ArgumentParser(description="Maintain reverse SSH tunnel for Asterisk IVR FastAGI.")
    parser.add_argument("--ssh-host", required=True)
    parser.add_argument("--ssh-port", type=int, default=22)
    parser.add_argument("--ssh-user", required=True)
    parser.add_argument("--ssh-password", required=True)
    parser.add_argument("--remote-bind-host", default="127.0.0.1")
    parser.add_argument("--remote-port", type=int, default=4573)
    parser.add_argument("--local-host", default="127.0.0.1")
    parser.add_argument("--local-port", type=int, default=4573)
    args = parser.parse_args()

    while True:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        try:
            client.connect(
                args.ssh_host,
                port=args.ssh_port,
                username=args.ssh_user,
                password=args.ssh_password,
                timeout=15,
                banner_timeout=15,
                auth_timeout=15,
            )
            transport = client.get_transport()
            if transport is None:
                raise RuntimeError("SSH transport is unavailable")
            transport.request_port_forward(args.remote_bind_host, args.remote_port)
            print(
                f"Reverse tunnel active: {args.remote_bind_host}:{args.remote_port} -> "
                f"{args.local_host}:{args.local_port}",
                flush=True,
            )
            _accept_channels(transport, args.local_host, args.local_port)
        except KeyboardInterrupt:
            return 0
        except Exception as exc:
            print(f"Reverse tunnel error: {exc}", file=sys.stderr, flush=True)
            time.sleep(3)
        finally:
            try:
                client.close()
            except Exception:
                pass


if __name__ == "__main__":
    raise SystemExit(main())
