#!/usr/bin/python3
"""Socket-activated PTY bridge to one validated managed container."""
import os
import pty
import selectors
import signal
import sys
from mykos_runtime import PODMAN, config, connection, request, target, containers

def main():
    conn=connection()
    if not config().get('terminal_enabled',False): raise ValueError('Terminal disabled in configuration')
    data=request(conn)
    if set(data) not in ({'shop'}, {'shop', 'action'}): raise ValueError('Expected shop and optional action')
    action = data.get('action', 'shell')
    if action not in ('shell', 'add-vpn', 'remove-vpn', 'image-shell'): raise ValueError('Unsupported terminal action')
    data_shop = data['shop']
    host = data_shop == 'host'
    image_shell = action == 'image-shell'
    if image_shell:
        from mykos_image_admin import base_container
        base_container(data_shop)
        name=data_shop
    else:
        name=None if host else target(data['shop'])
    if not host and not image_shell:
        from mykos_vm import record_path
        if record_path(data['shop']).is_file():
            from mykos_vm import read
            vm_os=read(data['shop']).get('os','')
            if not vm_os.startswith(('ubuntu','win','wns','mac')) or (action != 'shell' and not vm_os.startswith('ubuntu')):
                raise ValueError('Use the desktop browser console or RDP/VNC login for this VM')
    if not host and action in ('add-vpn','remove-vpn') and not any(item['name'] == name and item['state'].lower() == 'running' for item in containers()):
        raise ValueError('Start the container before adding a VPN')
    session_fd=None
    if image_shell:
        from mykos_base_session import acquire
        try:session_fd=acquire(data_shop)
        except ValueError as error:
            conn.sendall(('MYKOS_TERMINAL_ERROR '+str(error)+'\n').encode());conn.close();return
    pid,master=pty.fork()
    if pid==0:
        if host and action == 'remove-vpn':
            os.execv('/usr/bin/python3', ['/usr/bin/python3', '/usr/local/share/mykos/pos/host-vpn', '--remove'])
        elif host and action == 'add-vpn':
            os.execv('/usr/bin/python3', ['/usr/bin/python3', '/usr/local/share/mykos/pos/host-vpn'])
        elif host:
            os.execv('/bin/su', ['/bin/su', '-', 'root'])
        elif action in ('add-vpn','remove-vpn'):
            from mykos_vm import record_path,read
            if record_path(data['shop']).is_file() and read(data['shop']).get('os','').startswith('ubuntu'):
                os.execv(sys.executable,[sys.executable,'/usr/local/sbin/mykos-ubuntu-vpn',data['shop']]+(['--remove'] if action=='remove-vpn' else []))
            os.chdir('/usr/local/share/mykos/pos')
            os.execv('/usr/bin/bash', ['/usr/bin/bash', './pos', '--'+action, data['shop'], name])
        else:
            from mykos_vm import record_path,VERSIONS
            if (not image_shell or data_shop in VERSIONS) and record_path(data['shop']).is_file():
                from mykos_vm import read
                if read(data['shop'])['os'].startswith('ubuntu'):
                    from mykos_serial import open_terminal
                elif read(data['shop'])['os'].startswith('mac'):
                    from mykos_ssh import open_terminal
                else:
                    from mykos_powershell import open_terminal
                if image_shell: open_terminal(data['shop'], allow_image=True)
                else: open_terminal(data['shop'])
                raise SystemExit(0)
            if image_shell:
                from mykos_base_session import shell_command
                os.execv(PODMAN,[PODMAN,'exec','-it','--user','0',name,'/bin/su','--login','--shell','/bin/bash','--command',shell_command(),'root'])
            os.execv(PODMAN,[PODMAN,'exec','-it',name,'/bin/su','-','root'])
    def closing_signal(signum,frame):raise SystemExit(0)
    if image_shell:
        signal.signal(signal.SIGTERM,closing_signal)
        signal.signal(signal.SIGHUP,closing_signal)
    try:
        conn.sendall(b'MYKOS_TERMINAL_READY\n');conn.settimeout(None)
        with selectors.DefaultSelector() as selector:
            selector.register(conn,selectors.EVENT_READ)
            selector.register(master,selectors.EVENT_READ)
            while True:
                for key,_ in selector.select():
                    if key.fileobj==conn:
                        data=conn.recv(4096)
                        if not data:return
                        view=memoryview(data)
                        while view:
                            written=os.write(master,view);view=view[written:]
                    else:
                        try:data=os.read(master,4096)
                        except OSError:return
                        if not data:return
                        conn.sendall(data)
    except (ConnectionResetError,BrokenPipeError):
        pass
    finally:
        os.close(master);conn.close()
        try:os.kill(pid,signal.SIGTERM)
        except ProcessLookupError:pass
        import time
        deadline=time.monotonic()+1
        while True:
            try:
                if os.waitpid(pid,os.WNOHANG)[0]:break
            except ChildProcessError:break
            if time.monotonic()>=deadline:
                try:os.kill(pid,signal.SIGKILL);os.waitpid(pid,0)
                except (ProcessLookupError,ChildProcessError):pass
                break
            time.sleep(.05)
        if image_shell:
            try:
                from mykos_image_admin import queue_reset
                queue_reset(data_shop,data_shop,closing_session=True)
            except Exception as error:print('Base reset could not be queued: '+str(error),file=sys.stderr)
            finally:
                if session_fd is not None:os.close(session_fd)
        # A closed shell or VPN workflow may have changed this resource.
        try:
            from mykos_refresh import refresh
            if not image_shell: refresh('host' if host else data_shop)
        except Exception as error:
            print('Related terminal data refresh deferred: ' + str(error), file=sys.stderr)


if __name__=='__main__':
    try:main()
    except Exception as e:raise SystemExit(str(e))
