# Copyright (c) Ran Dugal 2023
#
# This file is part of picsh
#
# Licensed under the GNU Affero General Public License v3, which is available at
# http://www.gnu.org/licenses/agpl-3.0.html
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero GPL for more details.
#

import warnings

warnings.filterwarnings("ignore", module="asyncssh\.crypto.*")
from typing import Optional
import asyncssh


class SSHTargetNode(asyncssh.SSHClientSession):
    def __init__(self):
        self.recv_buf = ""
        self.hide = False
        self._is_connected = False
        self._conn = None
        self._session_factory = None
        self._chan = None
        self._notify = None

    def session_factory(self):
        return self

    def register_notify(self, notify_func):
        self._notify = notify_func

    async def do_connect(self):
        self.recv_buf = f"connecting {self.get_login_user()}@{self.get_ip()} ..."
        self._notify()
        self._conn = await asyncssh.connect(
            self.get_ip(),
            known_hosts=None,
            options=asyncssh.SSHClientConnectionOptions(
                client_keys=self.get_ssh_key_path(),
                username=self.get_login_user(),
                password=self.get_password(),
            ),
        )
        self._chan, sess = await self._conn.create_session(self.session_factory)
        self.recv_buf += "\n...connected"
        self._notify()
        self.recv_buf = ""

    async def run_cmd(self, cmd):
        try:
            if not self._is_connected:
                await self.do_connect()
                self._is_connected = True
            self._chan.write(cmd + "\n")

        except Exception as ex:
            self.recv_buf = "picsh: exception encountered: " + str(ex)
            self._notify()

    def data_received(self, data: str, datatype: asyncssh.DataType) -> None:
        self.recv_buf += data
        # if not self._login_guid_found:
        #     # surpress login banner.
        #     # Note: RFC-4254 reccomends the use of magic cookeis to surpress spurious
        #     # output when starting a subsystem via the shell "to distinguish it from
        #     # arbitrary output generated by shell initialization scripts, etc. This spurious
        #     # output from the shell may be filtered out either at the server or at the client"
        #     pos1 = self._node.recv_buf.find(InteractiveClientSession.login_complete_guid)
        #     if pos1 != -1:
        #         self._login_guid_found = True
        #         guid_len = len(InteractiveClientSession.login_complete_guid)
        #         self._node.recv_buf = self._node.recv_buf[pos1 + guid_len + 1:]
        if self.recv_buf:
            self._notify()

    def connection_lost(self, exc: Optional[Exception]) -> None:
        err_str = "\npicsh: Connection lost. "
        if exc:
            err_str += "SSH session error: " + str(exc)
        self.recv_buf += err_str
        self._notify()

    def get_ip(self):
        raise NotImplementedError()

    def get_login_user(self):
        raise NotImplementedError()

    def get_ssh_key_path(self):
        raise NotImplementedError()

    def get_password(self):
        raise NotImplementedError()


class Node(SSHTargetNode):
    def __init__(self):
        super().__init__()
        self.ip_addr = ""
        self.login_user = ""
        self.password = ""
        self.ssh_key_path = ""
        self.name = ""
        self.idx = -1

    def get_ip(self):
        return self.ip_addr

    def get_login_user(self):
        return self.login_user

    def get_ssh_key_path(self):
        return self.ssh_key_path

    def get_password(self):
        return self.password

    def __str__(self):
        return f"{self.login_user}@{self.ip_addr}"

    def __repr__(self):
        return self.__str__()
