import os
import platform as pf
import subprocess
from collections.abc import Mapping
from configparser import ConfigParser
from getpass import getuser
from logging import getLogger

from PySide6.QtCore import QProcess, QProcessEnvironment

if pf.system() != 'Windows':
    if pf.system() in {'Linux', 'FreeBSD'}:
        pass

logger = getLogger('CompatUtils')


# this is a copied function from legendary.utils.wine_helpers, but registry file can be specified
def read_registry(registry: str, prefix: str) -> ConfigParser:
    accepted = ['system.reg', 'user.reg']
    if registry not in accepted:
        raise RuntimeError(f'Unknown target "{registry}" not in {accepted}')
    reg = ConfigParser(comment_prefixes=(';', '#', '/', 'WINE'), allow_no_value=True, strict=False)
    reg.optionxform = str
    reg.read(os.path.join(prefix, 'system.reg'))
    return reg


def prepare_process(command: list[str], environment: dict) -> tuple[str, list[str], dict]:
    logger.debug('Preparing process: %s', command)
    _env = os.environ.copy()
    _command = command.copy()
    if os.environ.get('container') == 'flatpak':  # noqa: SIM112
        flatpak_command = ['flatpak-spawn', '--host']
        flatpak_command.extend(f'--env={name}={value}' for name, value in environment.items())
        _command = flatpak_command + command
    else:
        _env.update(environment)
    return _command[0], _command[1:] if len(_command) > 1 else [], _env


def dict_to_qprocenv(env: dict) -> QProcessEnvironment:
    _env = QProcessEnvironment()
    for name, value in env.items():
        _env.insert(name, value)
    return _env


def get_configured_qprocess(command: list[str], environment: dict) -> QProcess:
    cmd, args, env = prepare_process(command, environment)
    proc = QProcess()
    proc.setProcessChannelMode(QProcess.ProcessChannelMode.SeparateChannels)
    proc.setProcessEnvironment(dict_to_qprocenv(env))
    proc.setProgram(cmd)
    proc.setArguments(args)
    return proc


def get_configured_subprocess(command: list[str], environment: dict) -> subprocess.Popen:
    cmd, args, env = prepare_process(command, environment)
    return subprocess.Popen(
        (cmd, *args),
        stdin=None,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=env,
        shell=False,
        text=False,
    )


def execute_subprocess(command: list[str], arguments: list[str], environment: Mapping) -> tuple[str, str]:
    proc = get_configured_subprocess(command + arguments, environment)
    out, err = proc.communicate()
    out, err = (
        out.decode('utf-8', 'ignore') if out else '',
        err.decode('utf-8', 'ignore') if err else '',
    )

    # lk: the following is a work-around for wineserver sometimes hanging around after
    proc = get_configured_subprocess(command + ['wineboot', '-e'], environment)
    _, _ = proc.communicate()
    return out, err


def execute_qprocess(command: list[str], arguments: list[str], environment: Mapping) -> tuple[str, str]:
    logger.debug('WINEPREFIX: %s', environment.get('WINEPREFIX', None))
    logger.debug('STEAM_COMPAT_DATA_PATH: %s', environment.get('STEAM_COMPAT_DATA_PATH', None))
    proc = get_configured_qprocess(command + arguments, environment)
    proc.start()
    proc.waitForFinished(-1)
    out, err = (
        proc.readAllStandardOutput().data().decode('utf-8', 'ignore'),
        proc.readAllStandardError().data().decode('utf-8', 'ignore'),
    )
    proc.deleteLater()

    # lk: the following is a work-around for wineserver sometimes hanging around after
    proc = get_configured_qprocess(command + ['wineboot', '-e'], environment)
    proc.start()
    proc.waitForFinished(-1)
    proc.deleteLater()

    return out, err


def execute(command: list[str], arguments: list[str], environment: Mapping) -> tuple[str, str]:
    try:
        out, err = execute_qprocess(command, arguments, environment)
    except Exception as e:
        out, err = '', str(e)

    return out, err


def resolve_path(command: list[str], environment: Mapping, path: str) -> str:
    path = path.strip().replace('/', '\\')
    # lk: if path does not exist form
    arguments = ['c:\\windows\\system32\\cmd.exe', '/c', 'echo', path]
    # lk: if path exists and needs a case-sensitive interpretation form
    # cmd = [wine_cmd, 'cmd', '/c', f'cd {path} & cd']
    out, err = execute(command, arguments, environment)
    out, err = out.strip(), err.strip()
    if not out:
        logger.error('Failed to resolve wine path due to "%s"', err)
        return out
    return out.strip('"')


def query_reg_path(wine_exec: str, wine_env: Mapping, reg_path: str):
    raise NotImplementedError


def query_reg_key(command: list[str], environment: Mapping, reg_path: str, reg_key) -> str:
    arguments = ['c:\\windows\\system32\\reg.exe', 'query', reg_path, '/v', reg_key]
    out, err = execute(command, arguments, environment)
    out, err = out.strip(), err.strip()
    if not out:
        logger.error('Failed to query registry key due to "%s"', err)
        return out
    lines = out.split('\n')
    keys: dict = {}
    for line in lines:
        if line.startswith(' ' * 4):
            key = [x for x in line.split(' ' * 4, 3) if bool(x)]
            keys.update({key[0]: key[2]})
    return keys.get(reg_key, '')


def convert_to_windows_path(wine_exec: str, wine_env: Mapping, path: str) -> str:
    raise NotImplementedError


def convert_to_unix_path(command: list[str], environment: Mapping, path: str) -> str:
    path = path.strip().strip('"')
    arguments = ['c:\\windows\\system32\\winepath.exe', '-u', path]
    out, err = execute(command, arguments, environment)
    out, err = out.strip(), err.strip()
    if not out:
        logger.error('Failed to convert to unix path due to "%s"', err)
    return os.path.realpath(out) if (out := out.strip()) else out


def get_host_environment(app_environment: dict, silent: bool = False) -> dict:
    # Get a clean environment if we are in flatpak, this environment will be passed
    # to `flatpak-spawn`, otherwise use the system's.
    _environ = app_environment.copy()
    if silent:
        _environ['WINEESYNC'] = '0'
        _environ['WINEFSYNC'] = '0'
        _environ['WINE_DISABLE_FAST_SYNC'] = '1'
        _environ['WINEDEBUG'] = '-all'
        _environ['WINEDLLOVERRIDES'] = 'winemenubuilder=d;mscoree=d;mshtml=d;'
        _environ['WINEDLLOVERRIDES'] += 'winex11.drv,winewayland.drv=d;'
        # lk: pressure-vessel complains about this but it doesn't fail due to it
        # _environ["DISPLAY"] = ""
    return _environ


def create_compat_users(pfx: str):
    pfx_users = os.path.join(pfx, 'drive_c', 'users')
    os.makedirs(pfx_users, exist_ok=True)
    steam_user = os.path.join(pfx_users, 'steamuser')
    unix_user = os.path.join(pfx_users, getuser())
    if not os.path.exists(steam_user) and not os.path.exists(unix_user):
        os.makedirs(steam_user, exist_ok=True)
    pwd = os.getcwd()
    os.chdir(pfx_users)
    if os.path.exists(steam_user) and not os.path.exists(unix_user):
        os.symlink('steamuser', getuser())
    if not os.path.exists(steam_user) and os.path.exists(unix_user):
        os.symlink(getuser(), 'steamuser')
    os.chdir(pwd)
