from __future__ import annotations

import contextlib
import os.path
import re
import tempfile
import xml.etree.ElementTree
import zipfile
from collections.abc import Generator
from collections.abc import Sequence

from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.prefix import Prefix

ENVIRONMENT_DIR = 'dotnetenv'
BIN_DIR = 'bin'

get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
run_hook = lang_base.basic_run_hook


def get_env_patch(venv: str) -> PatchesT:
    return (
        ('PATH', (os.path.join(venv, BIN_DIR), os.pathsep, Var('PATH'))),
    )


@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    with envcontext(get_env_patch(envdir)):
        yield


@contextlib.contextmanager
def _nuget_config_no_sources() -> Generator[str]:
    with tempfile.TemporaryDirectory() as tmpdir:
        nuget_config = os.path.join(tmpdir, 'nuget.config')
        with open(nuget_config, 'w') as f:
            f.write(
                '<?xml version="1.0" encoding="utf-8"?>'
                '<configuration>'
                '  <packageSources>'
                '    <clear />'
                '  </packageSources>'
                '</configuration>',
            )
        yield nuget_config


def install_environment(
        prefix: Prefix,
        version: str,
        additional_dependencies: Sequence[str],
) -> None:
    lang_base.assert_version_default('dotnet', version)
    lang_base.assert_no_additional_deps('dotnet', additional_dependencies)

    envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
    build_dir = prefix.path('pre-commit-build')

    # Build & pack nupkg file
    lang_base.setup_cmd(
        prefix,
        (
            'dotnet', 'pack',
            '--configuration', 'Release',
            '--property', f'PackageOutputPath={build_dir}',
        ),
    )

    nupkg_dir = prefix.path(build_dir)
    nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')]

    if not nupkgs:
        raise AssertionError('could not find any build outputs to install')

    for nupkg in nupkgs:
        with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f:
            nuspec, = (x for x in f.namelist() if x.endswith('.nuspec'))
            with f.open(nuspec) as spec:
                tree = xml.etree.ElementTree.parse(spec)

        namespace = re.match(r'{.*}', tree.getroot().tag)
        if not namespace:
            raise AssertionError('could not parse namespace from nuspec')

        tool_id_element = tree.find(f'.//{namespace[0]}id')
        if tool_id_element is None:
            raise AssertionError('expected to find an "id" element')

        tool_id = tool_id_element.text
        if not tool_id:
            raise AssertionError('"id" element missing tool name')

        # Install to bin dir
        with _nuget_config_no_sources() as nuget_config:
            lang_base.setup_cmd(
                prefix,
                (
                    'dotnet', 'tool', 'install',
                    '--configfile', nuget_config,
                    '--tool-path', os.path.join(envdir, BIN_DIR),
                    '--add-source', build_dir,
                    tool_id,
                ),
            )
