#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import random
import re
import string
import subprocess
import tempfile
import traceback

from ..files.file_manager import FileManager
from ..files.path_in_file import PathInFile
from ..users import Users
from ..useful.useful import run_cmd


class SudoList(object):
    """
    Get rules and parse output from sudo -ll output
    """

    def __init__(self, password='test'):
        self.sudo_cmd = 'echo "{password}" | sudo -S -ll'.format(password=password)
        self.sudo_dirty_check = [
            'echo "{password}" | sudo -S -i'.format(password=password),
            # 'sudo -i'
        ]
        self.users = Users()
        self.all_rules = []
        self.ld_preload = False

    def dirty_check(self):
        for cmd in self.sudo_dirty_check:
            if run_cmd(cmd, is_ok=True):
                return 'sudo -i possible !'

    def _get_user(self, user):
        """
        Find a user pw object from his name
        - user is a string
        - u is an object
        """
        for u in self.users.list:
            if u.pw_name == user:
                return u
        return False

    def rules_from_sudo_ll(self):
        """
        Main function to retrieve sudoers rules from sudo -ll output
        """
        sudo_list, _ = run_cmd(self.sudo_cmd)
        if sudo_list:
            sudo_rules = self._parse_sudo_list(sudo_list.decode())
            self._impersonate_mechanism(self.users.current.pw_name, sudo_rules, users_chain=[])

        return self.all_rules

    def get_current_username(self):
        """Return the current username using the whoami command."""
        try:
            result = subprocess.run(['whoami'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            return result.stdout.strip()  # Strip to remove any trailing newline
        except Exception as e:
            print(f"Error getting username: {e}")
            return None

    def _parse_sudo_list(self, sudo_list):
        """
        Parse sudo -ll output
        """
        sudoers_info = []
        fm = FileManager('')
        
        if 'LD_PRELOAD' in sudo_list:
            self.ld_preload = True

        user = self.get_current_username()
        if not user:
            raise ValueError("Could not determine the current username.")
        sudoers_entries = sudo_list.lower().split('sudoers entry')
        for sudo_rule in sudoers_entries:

            if not sudo_rule.startswith(':'):
                continue

            pattern = re.compile(
                r"\s*" +
                "runasusers:\\s*(?P<runasusers>\\w*)" +
                "\\s*" +
                "(runasgroups:\\s*(?P<runasgroups>\\w*))*" +
                "\\s*" +
                "(options:\\s*(?P<options>[\\!\\w]*))*" +
                "\\s*" +
                "(commands:\\s*(?P<commands>.*))*",
                re.DOTALL
            )
            m = pattern.search(sudo_rule)
            # Default to empty string '' for values we didn't match
            data = m.groupdict('')
            # Remove whitespace and extra tabs from list of commands
            cmds = [PathInFile(line=cmd.strip(), paths=fm.extract_paths_from_string(cmd.strip()))
                    for cmd in data['commands'].strip().replace('\t', '').split('\n')]

            sudoers_info.append({
                'users': [user],
                'runas': data['runasusers'],
                'directives': data['options'],
                'cmds': cmds,
            })

        self.all_rules += sudoers_info
        return sudoers_info

    def _get_user_to_impersonate(self, sudo_rules):
        """
        Check if in the sudo rule, user impersonation is possible (using su bin)
        """
        users = []
        for rules in sudo_rules:
            for cmd in rules['cmds']:
                for c in cmd.paths:
                    if c.basename == 'su':
                        # Do not perform further checks as it's already to impersonate root user
                        args = cmd.line.strip()[cmd.line.strip().index(c.basename) + len(c.basename):].strip()
                        if args.strip() and args.strip() not in ['root', '*']:
                            u = self._get_user(args.strip())
                            if u:
                                users.append(u)
        return users

    def _impersonate_user(self, users_chain=[]):
        """
        Get the user to impersonate and return his sudo -l output

        For example:
        - The current user has "su" rule to impersonate user A
        - The user A can impersonate user B (but the current user cannot)
        - User B has root privilege
        => users_chain = ["user A", "user B"]

        sudo -l return only rules concerning the user launching this command. 
        
        The trick is to use a temporary file like following: 
        sudo su test << 'EOF'         
        echo "test" | sudo -S -l
        EOF
        """
        data = ''
        for u in users_chain:
            data += "sudo su {user} << 'EOF'\n".format(user=u)
        data += self.sudo_cmd + '\n'
        
        if users_chain: 
            data += '\nEOF'

        rand = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
        path = os.path.join(tempfile.gettempdir(), rand) + '.sh'
        with open(path, 'w') as file:
            file.write(data)

        if os.path.exists(path):
            out = run_cmd(cmd='chmod +x {path}'.format(path=path), is_ok=True)
            if out:
                out, _ = run_cmd(cmd=path)
            os.remove(path)
            return out

    def _impersonate_mechanism(self, user, sudo_rules, users_chain=[], already_impersonated=[]):
        """
        Recursive function to retrieve all sudo rules
        All rules for all possible users are stored on "all_rules"
        """
        for u in self._get_user_to_impersonate(sudo_rules):
            if u not in already_impersonated:
                sudo_list = self._impersonate_user(users_chain=[user, u])
                if sudo_list:
                    try:
                        sudo_rules = self._parse_sudo_list(sudo_list)
                        self._impersonate_mechanism(u, sudo_rules, [user, u], already_impersonated)
                    except Exception:
                        print(traceback.format_exc())
                        continue

                    already_impersonated.append(u)
