import time

from packaging.specifiers import SpecifierSet

import wox.configurator as configurator
import wox.python_discoverer as python_discoverer
import wox.subprocessor as subprocessor
import wox.wox_summary as wox_summary
import wox.venv_management as venv_manager

from wox.constants import (
    DOT_WOX,
    MSV_UV,
    ExitCode
)
from wox.logger.filters import WoxContext
from wox.logger.logger_setup import wox_logger
from wox.wox_exceptions import MissingWoxKeywordError, NotPythonVenvWarning


def main(cli_options, wox_data):
    """
        Entrypoint for a wox session.

        Parameters
        ----------
        cli_options : dict
            Options provided on the command line.
        wox_data : dict
            Contains informations provided by the .toml file.

        Returns
        -------
        exit_code : int

        Raises
        ------
        Exception
            Any exceptions caught during the session.
    """

    start_time = time.perf_counter()
    exit_code = ExitCode.SUCCESS.value
    wox_session_summary = []

    logger_extra_fields = WoxContext()
    wox_logger.addFilter(logger_extra_fields)
    if cli_options.get('verbose', False):
        wox_logger.setLevel('DEBUG')

    wox_logger.debug(f'wox launched with options {cli_options}')

    try:
        wox_configuration, tasks = configurator.session_setup(cli_options, wox_data)
    except Exception:
        raise

    with_uv = wox_configuration.get('with_uv', False)

    wox_logger.debug('Wox session initialized with:')
    wox_logger.debug(f'- wox configuration {wox_configuration} ;')
    wox_logger.debug(f'- task settings {tasks} ;')

    if wox_configuration.get('recreate_envs', None):
        venv_manager.venv_deletion(tasks)

    for task, settings in tasks.items():

        try:
            envlist = tasks[task].pop('envlist')
        except KeyError as e:
            raise MissingWoxKeywordError(e)

        for env in envlist:

            logger_extra_fields.update_context({'task': task, 'env': env})

            execution_unit = {'execution_unit': f'{task}:{env}'}

            config = configurator.configuration_creation(env, settings, envlist)

            env_path = DOT_WOX / env

            if not env_path.exists():
                if (python_cmd := config.get('python_build')):
                    python_cmd = config['python_build']

                    wox_logger.debug(f"Use of specified python build '{python_cmd}'.")

                try:
                    python_interpreter = python_discoverer.python_discovery(
                        env, python_cmd
                    )
                except Exception as e:

                    wox_summary.update_execution_unit_failure(
                        execution_unit,
                        'Python discovery',
                        e
                    )
                    wox_session_summary.append(execution_unit)

                    wox_logger.error(e)
                    exit_code = ExitCode.RUNTIME_ERROR.value
                    continue

                try:
                    venv_manager.create_venv(env_path, python_interpreter, with_uv)
                except Exception as e:

                    wox_summary.update_execution_unit_failure(
                        execution_unit,
                        'Venv creation',
                        e
                    )

                    wox_session_summary.append(execution_unit)

                    wox_logger.error(e)
                    exit_code = ExitCode.RUNTIME_ERROR.value
                    continue

            try:
                venv_python_interpreter = python_discoverer.locate_venv_python_exe(env_path)
            except NotPythonVenvWarning as e:
                venv_python_interpreter = None
                wox_logger.warning(e)

            deps = ' '.join(config.get('deps', []))
            if deps:
                try:
                    if with_uv and venv_python_interpreter.version in SpecifierSet(MSV_UV):
                        subprocessor.run_command(
                            (f"uv pip install --python {venv_python_interpreter.path} "
                            f"{'-qqq' if not cli_options.get('verbose', False) else ''} "
                            f"{deps}"),
                            python_interpreter = venv_python_interpreter
                        )
                    else:
                        subprocessor.run_command(
                            (f"{venv_python_interpreter.path} -I -m pip install "
                            f"{'-qqq' if not cli_options.get('verbose', False) else ''} "
                            f"-U pip"),
                            python_interpreter = venv_python_interpreter
                        )
                        subprocessor.run_command(
                            (f"{venv_python_interpreter.path} -I -m pip install "
                            f"{'-qqq' if not cli_options.get('verbose', False) else ''} "
                            f"{deps}"),
                            python_interpreter = venv_python_interpreter
                        )
                except Exception as e:

                    wox_summary.update_execution_unit_failure(
                        execution_unit,
                        'Dependencies installation',
                        e
                    )
                    wox_session_summary.append(execution_unit)

                    wox_logger.error(e)
                    exit_code = ExitCode.RUNTIME_ERROR.value
                    continue

            for command in config.get('commands', []):
                try:
                    subprocessor.run_command(
                        command,
                        python_interpreter = venv_python_interpreter
                    )
                except Exception as e:

                    wox_summary.update_execution_unit_failure(
                        execution_unit,
                        'Commands execution',
                        e
                    )
                    wox_session_summary.append(execution_unit)

                    wox_logger.error(e)
                    exit_code = ExitCode.RUNTIME_ERROR.value
                    continue

            wox_summary.update_execution_unit_success(execution_unit)

            wox_session_summary.append(execution_unit)

    duration = round((time.perf_counter() - start_time), 3)
    wox_summary.render(wox_session_summary, duration)

    return exit_code
