# from dbus import SystemBus, Interface, DBusException, mainloop
import dbus, sys, threading

from gi.repository import GObject, GLib

from typing import Iterable, Dict, Callable
from DomainjoinGUI.utils import call_async, OnSuccessFunction, OnErrorFunction

INTERFACE_NAME="cu.nova.domainjoin.controller"

class DomainController:
    OBJECT_PATH = "/cu/nova/domainjoin/controller"

    def __init__(self):
        bus = dbus.SystemBus()
        try:
            self.proxy = bus.get_object(INTERFACE_NAME, self.OBJECT_PATH)
            self.iface = dbus.Interface(self.proxy, INTERFACE_NAME)
        except dbus.DBusException:
            sys.exit(1)

        self._domains = []
        self.load_domains()

    def load_domains(self):
        domains_obj = self.iface.GetAll()
        self._domains = [Domain(x) for x in domains_obj]

    def get_all_domains(self):
        return self._domains

    def join_domain(self, domain, user, password):
        err = self.iface.Join(domain, user, password)
        if err == "":
            return {
                'exit_code': 0,
            }
        else:
            return {
                'exit_code': 1,
                'error': err
            }

    @call_async(sync_function=join_domain)
    def join_domain_async(self, domain, user, password, on_success: OnSuccessFunction, on_error: OnErrorFunction):
        pass

class DomainUser(GObject.GObject):
    __gsignals__ = {
        'user-changed': (GObject.SIGNAL_RUN_FIRST, None,())
    }
    
    def __init__(self, username, is_group, is_admin):
         GObject.GObject.__init__(self)

         self.username = username
         self.is_group = is_group
         self.is_admin = is_admin


class Domain(GObject.GObject):

    __gsignals__ = {
        'user-added': (GObject.SIGNAL_RUN_FIRST, None,()),
        'user-removed': (GObject.SIGNAL_RUN_FIRST, None,()),
        'user-changed': (GObject.SIGNAL_RUN_FIRST, None,())
    }

    def __init__(self, path):
        GObject.GObject.__init__(self)
        bus = dbus.SystemBus()
        try:
            self.proxy = bus.get_object(INTERFACE_NAME, path)
            self.iface = dbus.Interface(self.proxy, INTERFACE_NAME)
        except dbus.DBusException:
            sys.exit(1)

    @property
    def domain_name(self):
        return self.iface.GetDomainName()

    @property
    def users(self):
        return [DomainUser(
            username=x['user'],
            is_admin=x['is_admin'],
            is_group=x['is_group'],
        ) for x in self.iface.GetUsers()]

    @property
    def domain_info(self):
        return self.iface.GetDomainInfo()

    @property
    def use_fqdn(self):
        return self.iface.GetUseFQDN()

    @use_fqdn.setter
    def use_fqdn(self, value: bool):
        self.iface.SetUseFQDN(value)

    def add_user(self, username, is_group, is_admin):
        success = self.iface.AddUser(username, is_group, is_admin)
        if success:
            self.emit('user-added')

        # TODO: Force the service to explain the error
        return {
            'exit_code': 0 if success else 1,
            'error': '' if success else 'Failed to add user'
        }

    @call_async(sync_function=add_user)
    def add_user_async(self, username, is_group, is_admin, on_success: OnSuccessFunction, on_error: OnErrorFunction):
        pass

    def remove_user(self, user):
        username = user.username
        success = self.iface.RemoveUser(username)
        if success:
            self.emit('user-removed')
        
        # TODO: Force the service to explain the error
        return {
            'exit_code': 0 if success else 1,
            'error': '' if success else 'Failed to remove user'
        }

    @call_async(sync_function=remove_user)
    def remove_user_async(self, username, on_success: OnSuccessFunction, on_error: OnErrorFunction):
        pass

    def leave(self):
        success = self.iface.Leave()
        # TODO: Force the service to explain the error
        return {
            'exit_code': 0 if success else 1,
            'error': '' if success else 'Failed to leave domain'
        }

    @call_async(sync_function=leave)
    def leave_async(self, on_success: OnSuccessFunction, on_error: OnErrorFunction):
        pass

    def set_user_admin_status(self, user: DomainUser, is_admin: bool):
        username = user.username
        success = self.iface.SetUserType(username, is_admin)
        if success:
            self.emit(
                'user-changed'
            )
        return {
            'exit_code': 0 if success else 1,
            'error': '' if success else 'Failed to set admin permission to ' + username
        }

    @call_async(sync_function=set_user_admin_status)
    def set_user_admin_status_async(self, user: DomainUser, is_admin: bool, on_success: OnSuccessFunction, on_error: OnErrorFunction):
        pass

domain_controller = None
def get_domain_controller():
    global domain_controller
    if not domain_controller:
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        domain_controller = DomainController()
    return domain_controller
