import os
import tempfile
import paramiko
import time
from datetime import datetime

from .config import config
from .ctx import app_service
from .connection import SSHConnection


class SFTPServer(paramiko.SFTPServerInterface):
    root = '/tmp'

    def __init__(self, server, **kwargs):
        super().__init__(server, **kwargs)
        self.server = server
        self._sftp = {}
        self.hosts = self.get_perm_hosts()

    def session_ended(self):
        super().session_ended()
        for _, v in self._sftp.items():
            sftp = v['sftp']
            sock = v.get('sock')
            chan = sftp.get_channel()
            trans = chan.get_transport()
            sftp.close()

            active_channels = [c for c in trans._channels.values() if not c.closed]
            if not active_channels:
                print("CLose transport")
                trans.close()
                if sock:
                    sock.close()
                    sock.transport.close()
        self._sftp = {}

    def get_host_sftp(self, host, su):
        asset = self.hosts.get(host)
        system_user = None
        for s in self.get_asset_system_users(host):
            if s.name == su:
                system_user = s
                break

        if not asset or not system_user:
            raise OSError("No asset or system user explicit")

        if host not in self._sftp:
            ssh = SSHConnection()
            sftp, sock, msg = ssh.get_sftp(asset, system_user)
            if sftp:
                self._sftp[host] = {'sftp': sftp, 'sock': sock}
                return sftp
            else:
                raise OSError("Can not connect asset sftp server: {}".format(msg))
        else:
            return self._sftp[host]['sftp']

    def get_perm_hosts(self):
        hosts = {}
        assets = app_service.get_user_assets(
            self.server.connection.user
        )
        for asset in assets:
            key = asset.hostname
            if asset.org_id:
                key = "{}.{}".format(asset.hostname, asset.org_name)
            hosts[key] = asset
        return hosts

    def parse_path(self, path):
        data = path.lstrip('/').split('/')
        su = rpath = ''
        if len(data) == 1:
            host = data[0]
        elif len(data) == 2:
            host, su = data
            rpath = self.root
        else:
            host, su, *rpath = data
            rpath = os.path.join(self.root, '/'.join(rpath))
        return host, su, rpath

    def get_sftp_rpath(self, path):
        host, su, rpath = self.parse_path(path)
        sftp = self.get_host_sftp(host, su) if host and su else None
        return sftp, rpath

    def get_asset_system_users(self, host):
        asset = self.hosts.get(host)
        if not asset:
            return []
        return [su for su in asset.system_users_granted if su.protocol == "ssh"]

    def su_in_asset(self, su, host):
        system_users = self.get_asset_system_users(host)
        if su in [s.name for s in system_users]:
            return True
        else:
            return False

    def create_ftp_log(self, path, operate, is_success=True, filename=None):
        host, su, rpath = self.parse_path(path)
        asset = self.hosts.get(host)
        date_start = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " +0000",
        data = {
            "user": self.server.connection.user.username,
            "asset": host,
            "org_id": asset.org_id,
            "system_user": su,
            "remote_addr": self.server.connection.addr[0],
            "operate": operate,
            "filename": filename or rpath,
            "date_start": date_start,
            "is_success": is_success,
        }
        for i in range(1, 4):
            ok = app_service.create_ftp_log(data)
            if ok:
                break
            else:
                time.sleep(0.5)
                continue

    @staticmethod
    def stat_host_dir():
        tmp = tempfile.TemporaryDirectory()
        attr = paramiko.SFTPAttributes.from_stat(os.stat(tmp.name))
        tmp.cleanup()
        return attr

    def list_folder(self, path):
        output = []
        host, su, rpath = self.parse_path(path)
        if not host:
            for hostname in self.hosts:
                attr = self.stat_host_dir()
                attr.filename = hostname
                output.append(attr)
        elif not su:
            for su in self.get_asset_system_users(host):
                attr = self.stat_host_dir()
                attr.filename = su.name
                output.append(attr)
        else:
            sftp, rpath = self.get_sftp_rpath(path)
            file_list = sftp.listdir(rpath)
            for filename in file_list:
                attr = sftp.stat(os.path.join(rpath, filename))
                attr.filename = filename
                output.append(attr)
        return output

    def stat(self, path):
        host, su, rpath = self.parse_path(path)

        e = OSError("Not that dir")
        if host and host not in self.hosts:
            return paramiko.SFTPServer.convert_errno(e.errno)
        if su and not self.su_in_asset(su, host):
            return paramiko.SFTPServer.convert_errno(e.errno)

        if not rpath or rpath == "/":
            attr = self.stat_host_dir()
            attr.filename = su or host
            return attr
        else:
            sftp = self.get_host_sftp(host, su)
            try:
                stat = sftp.stat(rpath)
                return stat
            except FileNotFoundError as e:
                return paramiko.SFTPServer.convert_errno(e.errno)

    def lstat(self, path):
        host, su, rpath = self.parse_path(path)

        if not host or not su or not rpath or rpath == "/":
            attr = self.stat_host_dir()
            attr.filename = su or host
        else:
            sftp = self.get_host_sftp(host, su)
            attr = sftp.stat(rpath)
            attr.filename = os.path.basename(path)
        return attr

    def open(self, path, flags, attr=None):
        binary_flag = getattr(os, 'O_BINARY', 0)
        flags |= binary_flag
        success = False

        if flags & os.O_WRONLY:
            if flags & os.O_APPEND:
                mode = 'ab'
            else:
                mode = 'wb'
        elif flags & os.O_RDWR:
            if flags & os.O_APPEND:
                mode = 'a+b'
            else:
                mode = 'r+b'
        else:
            mode = 'rb'

        sftp, rpath = self.get_sftp_rpath(path)
        if 'r' in mode:
            operate = "Download"
        else:
            operate = "Upload"

        result = None
        if sftp is not None:
            try:
                f = sftp.open(rpath, mode, bufsize=4096)
                obj = paramiko.SFTPHandle(flags)
                obj.filename = rpath
                obj.readfile = f
                obj.writefile = f
                result = obj
                success = True
            except OSError:
                pass
        self.create_ftp_log(path, operate, success)
        return result

    def remove(self, path):
        sftp, rpath = self.get_sftp_rpath(path)
        success = False

        if sftp is not None:
            try:
                sftp.remove(rpath)
            except OSError as e:
                result = paramiko.SFTPServer.convert_errno(e.errno)
            else:
                result = paramiko.SFTP_OK
                success = True
        else:
            result = paramiko.SFTP_FAILURE
        self.create_ftp_log(path, "Delete", success)
        return result

    def rename(self, src, dest):
        host1, su1, rsrc = self.parse_path(src)
        host2, su2, rdest = self.parse_path(dest)
        success = False

        if host1 == host2 and su1 == su2 and host1:
            sftp = self.get_host_sftp(host1, su1)
            try:
                sftp.rename(rsrc, rdest)
                success = True
            except OSError as e:
                result = paramiko.SFTPServer.convert_errno(e.errno)
            else:
                result = paramiko.SFTP_OK
        else:
            result = paramiko.SFTP_FAILURE
        filename = "{}=>{}".format(rsrc, rdest)
        self.create_ftp_log(src, "Rename", success, filename=filename)
        return result

    def mkdir(self, path, attr=0o755):
        sftp, rpath = self.get_sftp_rpath(path)
        success = False

        if sftp is not None and rpath != '/':
            try:
                sftp.mkdir(rpath)
                success = True
            except OSError as e:
                result = paramiko.SFTPServer.convert_errno(e.errno)
            else:
                result = paramiko.SFTP_OK
        else:
            result = paramiko.SFTP_FAILURE
        self.create_ftp_log(path, "Mkdir", success, filename=rpath)
        return result

    def rmdir(self, path):
        sftp, rpath = self.get_sftp_rpath(path)
        success = False

        if sftp is not None:
            try:
                sftp.rmdir(rpath)
                success = True
            except OSError as e:
                result = paramiko.SFTPServer.convert_errno(e.errno)
            else:
                result = paramiko.SFTP_OK
        else:
            result = paramiko.SFTP_FAILURE
        self.create_ftp_log(path, "Rmdir", success)
        return result

    # def chattr(self, path, attr):
    #     sftp, rpath = self.get_sftp_rpath(path)
    #     if sftp is not None:
    #         if attr._flags & attr.FLAG_PERMISSIONS:
    #             sftp.chmod(rpath, attr.st_mode)
    #         if attr._flags & attr.FLAG_UIDGID:
    #             sftp.chown(rpath, attr.st_uid, attr.st_gid)
    #         if attr._flags & attr.FLAG_AMTIME:
    #             sftp.utime(rpath, (attr.st_atime, attr.st_mtime))
    #         if attr._flags & attr.FLAG_SIZE:
    #             sftp.truncate(rpath, attr.st_size)
    #         return paramiko.SFTP_OK


class FakeServer:
    pass


class FakeTransport:
    _trans = None

    @staticmethod
    def getpeername():
        return '127.0.0.1', config['SSHD_PORT']

    @staticmethod
    def get_username():
        return 'fake'


class FakeChannel:
    _chan = None

    def get_transport(self):
        return FakeTransport()

    @classmethod
    def new(cls):
        if not cls._chan:
            cls._chan = cls()
        return cls._chan


class InternalSFTPClient(SFTPServer):
    def __init__(self, connection):
        fake_server = FakeServer()
        fake_server.connection = connection
        super().__init__(fake_server)

    def listdir_attr(self, path):
        return self.list_folder(path)

    def open(self, path, mode, **kwargs):
        sftp, rpath = self.get_sftp_rpath(path)
        if 'r' in mode:
            operate = "Download"
        else:
            operate = "Upload"

        success = False
        if sftp is not None:
            try:
                f = sftp.open(rpath, mode, bufsize=4096)
            except OSError:
                raise OSError("Open file {} failed".format(rpath))
            finally:
                print("Open success")
                self.create_ftp_log(path, operate, success)
            return f

    def get_channel(self):
        return FakeChannel.new()

    def unlink(self, path):
        return self.remove(path)

    def close(self):
        print("End ")
        return self.session_ended()
