import json
import os
import re
import sys

import pytest

from hatch.utils.structures import EnvVars
from hatch.venv.core import VirtualEnv


def test_initialization_does_not_create(temp_dir, platform):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)

    assert not venv.exists()

    with pytest.raises(OSError, match=f'Unable to locate executables directory within: {re.escape(str(venv_dir))}'):
        _ = venv.executables_directory


def test_remove_non_existent_no_error(temp_dir, platform):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.remove()


def test_creation(temp_dir, platform):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.create(sys.executable)

    assert venv_dir.is_dir()
    assert venv.exists()


def test_executables_directory(temp_dir, platform):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.create(sys.executable)

    assert venv.executables_directory.is_dir()
    for entry in venv.executables_directory.iterdir():
        if entry.name.startswith('py'):
            break
    else:  # no cov
        msg = 'Unable to locate Python executable'
        raise AssertionError(msg)


def test_activation(temp_dir, platform):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.create(sys.executable)

    with EnvVars(exclude=VirtualEnv.IGNORED_ENV_VARS):
        os.environ['PATH'] = str(temp_dir)
        os.environ['VIRTUAL_ENV'] = 'foo'
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            os.environ[env_var] = 'foo'

        venv.activate()

        assert os.environ['PATH'] == f'{venv.executables_directory}{os.pathsep}{temp_dir}'
        assert os.environ['VIRTUAL_ENV'] == str(venv_dir)
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            assert env_var not in os.environ

        venv.deactivate()

        assert os.environ['PATH'] == str(temp_dir)
        assert os.environ['VIRTUAL_ENV'] == 'foo'
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            assert os.environ[env_var] == 'foo'


def test_activation_path_env_var_missing(temp_dir, platform):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.create(sys.executable)

    with EnvVars(exclude=VirtualEnv.IGNORED_ENV_VARS):
        os.environ.pop('PATH', None)
        os.environ['VIRTUAL_ENV'] = 'foo'
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            os.environ[env_var] = 'foo'

        venv.activate()

        assert os.environ['PATH'] == f'{venv.executables_directory}{os.pathsep}{os.defpath}'
        assert os.environ['VIRTUAL_ENV'] == str(venv_dir)
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            assert env_var not in os.environ

        venv.deactivate()

        assert 'PATH' not in os.environ
        assert os.environ['VIRTUAL_ENV'] == 'foo'
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            assert os.environ[env_var] == 'foo'


def test_context_manager(temp_dir, platform, extract_installed_requirements):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.create(sys.executable)

    with EnvVars(exclude=VirtualEnv.IGNORED_ENV_VARS):
        os.environ['PATH'] = str(temp_dir)
        os.environ['VIRTUAL_ENV'] = 'foo'
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            os.environ[env_var] = 'foo'

        with venv:
            assert os.environ['PATH'] == f'{venv.executables_directory}{os.pathsep}{temp_dir}'
            assert os.environ['VIRTUAL_ENV'] == str(venv_dir)
            for env_var in VirtualEnv.IGNORED_ENV_VARS:
                assert env_var not in os.environ

            # Run here while we have cleanup
            output = platform.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8')
            assert not extract_installed_requirements(output.splitlines())

        assert os.environ['PATH'] == str(temp_dir)
        assert os.environ['VIRTUAL_ENV'] == 'foo'
        for env_var in VirtualEnv.IGNORED_ENV_VARS:
            assert os.environ[env_var] == 'foo'


def test_creation_allow_system_packages(temp_dir, platform, extract_installed_requirements):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.create(sys.executable, allow_system_packages=True)

    with venv:
        output = platform.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8')

        assert len(extract_installed_requirements(output.splitlines())) > 0


def test_python_data(temp_dir, platform):
    venv_dir = temp_dir / 'venv'
    venv = VirtualEnv(venv_dir, platform)
    venv.create(sys.executable)

    with venv:
        output = platform.run_command(
            ['python', '-W', 'ignore', '-'],
            check=True,
            capture_output=True,
            input=b'import json,sys;print(json.dumps([path for path in sys.path if path]))',
        ).stdout.decode('utf-8')

        assert venv.environment is venv.environment
        assert venv.sys_path is venv.sys_path

        assert venv.environment['sys_platform'] == sys.platform
        assert venv.sys_path == json.loads(output)
