#!/usr/bin/python3

import configparser, dbus

from subprocess import Popen, PIPE
from typing import Tuple

account_service_client = None

SERVICE = "org.freedesktop.Accounts"
USER_INTERFACE = "org.freedesktop.Accounts.User"
OBJECT_PATH = "/org/freedesktop/Accounts"

ACCOUNT_TYPE_ADMIN = 1
ACCOUNT_TYPE_NORMAL = 0

def get_dbus(service, path, interface=None):
    if interface is None:
        interface = service
    bus = dbus.SystemBus()
    proxy = bus.get_object(service, path)
    interface = dbus.Interface(proxy, interface)
    properties = dbus.Interface(interface, 'org.freedesktop.DBus.Properties')
    return (proxy, interface, properties)


def get_account_client():
    global account_service_client
    if account_service_client is None:
        account_service_client = get_dbus(SERVICE, OBJECT_PATH)

    return account_service_client


def get_account(username):
    _, iface, _ = get_account_client()

    user = iface.FindUserByName(username)
    return get_dbus(SERVICE, user, USER_INTERFACE)


def get_user_details(username):
    _, _, user_props = get_account(username)

    return {
        'username': str(user_props.Get(USER_INTERFACE, 'UserName')),
        'local-account': bool(user_props.Get(USER_INTERFACE, 'LocalAccount')),
        'account-type': int(user_props.Get(USER_INTERFACE, 'AccountType')),
        'real-name': str(user_props.Get(USER_INTERFACE, 'RealName')),
        'home-directory': str(user_props.Get(USER_INTERFACE, 'HomeDirectory'))
    }


def set_user_type(username, account_type):
    try:
        _, user_iface, _ = get_account(username)
        user_iface.SetAccountType(account_type)
        return True
    except Exception as e:
        print('Failed to set admin: ' + str(e))
        return False


def _run_cmd(cmd, **input_data):
    process = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True, text=True)
    out, err = process.communicate(
        **input_data
    )

    return process.returncode, out, err


def join_domain(domain: str, user: str, password: str) -> Tuple[int, str]:
    cmd = "realm join -U {u} {d}".format(
        u=user,
        d=domain
    )
    try:
        process = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True, text=True)
        _, err = process.communicate(
            input=password
        )

        return_code = process.returncode
        if return_code == 0:
            return 0, ''
        else:
            return return_code, err
    except:
        return 1, 'Unknown error'


def leave_domain(domain: str) -> Tuple[int, str]:
    cmd = "realm leave -v {d}".format(
        d=domain
    )
    try:
        process = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True, text=True)
        _, err = process.communicate()

        return_code = process.returncode
        if return_code == 0:
            return 0, ''
        else:
            return return_code, err
    except:
        return 1, 'Unknown error'


def restart_service() -> Tuple[int, str]:
    cmd = "service sssd restart"
    try:
        process = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True, text=True)
        _, err = process.communicate()

        return_code = process.returncode
        if return_code == 0:
            return 0, ''
        else:
            return return_code, err
    except:
        return 1, 'Unknown error'


def get_all_domains():
    _, out, _ = _run_cmd('realm list')

    domains = {}
    lines = out.splitlines()
    i = 0
    while i < len(lines):
        line = lines[i]
        if not line.startswith(' '):
            domain_name = line.strip()
            domain_dict = {}
            i += 1
            while i < len(lines) and len(lines[i]) > 0 and lines[i].startswith(' '):
                current = lines[i].strip().split(':')
                domain_dict[current[0].strip()] = current[1].strip()
                i += 1
            domains[domain_name] = domain_dict
    return domains


def add_user(domain, user, is_group, is_admin):
    if user.find('@') > 0:
        user = user.split('@')[0]
        
    cmd = "realm permit {group} '{user}'".format(
        group='-g' if is_group else '',
        user="%s@%s" % (user, domain)
    )

    exit_code, _, _ = _run_cmd(cmd)
    if exit_code == 0:
        try:
            set_user_type(user, ACCOUNT_TYPE_ADMIN if is_admin else ACCOUNT_TYPE_NORMAL)
        except Exception as e:
            print('Failed to set admin: ' + str(e))
    
    return exit_code == 0


def remove_user(domain, user):
    domains = get_all_domains()
    if domain in domains:
        domain_data = domains[domain]
        exit_code, _, _ = _run_cmd('realm deny -a -R %s' % domain)

        if exit_code != 0:
            return False

        allowed_user = domain_data['permitted-logins'].strip() if 'permitted-logins' in domain_data else ''
        allowed_group = domain_data['permitted-groups'].strip() if 'permitted-groups' in domain_data else ''
        allowed_user_list = allowed_user.split(', ') if allowed_user != '' else []
        allowed_groups_list = allowed_group.split(', ') if allowed_group != '' else []

        if len(allowed_user_list) + len(allowed_groups_list) <= 1:
            _run_cmd('realm permit -a -R %s' % domain)
        else:
            for u in allowed_user_list:
                if u == user or "{}@{}".format(user, domain) == u:
                    continue
                # TODO: verify if user is admin
                add_user(domain, u.split('@')[0], is_group=False, is_admin=True)

            for g in allowed_groups_list:
                if g == user or "{}@{}".format(user, domain) == g:
                    continue
                # TODO: verify if group is admin
                add_user(domain, g.split('@')[0], is_group=True, is_admin=True)
        return True

    return False


def set_use_fqdn(domain, use_fqdn):
    sssd_conf = '/etc/sssd/sssd.conf'
    config = configparser.RawConfigParser()
    config.read(sssd_conf)

    key = 'domain/%s' % domain
    if key in config:
        config[key]['use_fully_qualified_names'] = str(use_fqdn)
        config[key]['fallback_homedir'] = '/home/%u@%d' if use_fqdn else '/home/%u'
        with open(sssd_conf, 'w') as f:
            config.write(f)
        return True
        
    return False


def get_users(domain_name):
    domains = get_all_domains()
    if domain_name in domains:
        domain_data = domains[domain_name]
        if 'login-policy' in domain_data and domain_data['login-policy'] == 'allow-permitted-logins':
            users_value = domain_data['permitted-logins']
            groups_value = domain_data['permitted-groups']
            users = users_value.split(', ') if users_value != '' else []
            groups = groups_value.split(', ') if groups_value != '' else []

            all_users = []
            for u in users:
                info = {
                    'user': u, 
                    'is_group': False, 
                    'is_admin': False
                }

                try:
                    user_details = get_user_details(u)
                    info.update({
                        'is_admin': user_details['account-type'] == ACCOUNT_TYPE_ADMIN,
                        'real_name': user_details['real-name'],
                        'home_directory': user_details['home-directory'],
                    })
                except Exception as e:
                    print('Failed to get user information: ' + str(e))
                all_users.append(info)

            all_users += [{'user': x, 'is_group': True, 'is_admin': False} for x in groups]
            return all_users 
    
    return []


if __name__ == '__main__':
    add_user('uci.cu', 'sec-ucitrabajador', True, True)
