import io
import os
import platform
import re
import sys
import sysconfig
from tarfile import TarFile
from tarfile import TarInfo
from typing import Generator
from urllib.parse import urljoin
from urllib.request import urlopen
from zipfile import ZipFile

import pre_commit.constants as C

ARCHITECTURES = {
    'x86': 'x86',  # Windows Vista 32
    'i686': 'x86',
    'x86_64': 'x64',  # Linux Ubuntu 64
    'amd64': 'x64',  # FreeBSD 64bits
    'AMD64': 'x64',  # Windows Server 2012 R2 (x64)
    'armv6l': 'armv6l',  # arm
    'armv7l': 'armv7l',
    'armv8l': 'armv7l',
    'aarch64': 'arm64',
    'arm64': 'arm64',
    'arm64/v8': 'arm64',
    'armv8': 'arm64',
    'armv8.4': 'arm64',
    'ppc64le': 'ppc64le',  # Power PC
    's390x': 's390x',  # IBM S390x
}

NIX_NODE_SUBDIRS = re.compile(r'node[^/]+/(bin|lib|include|share)')
WINDOWS_NODE_SUBDIRS = re.compile(r'node[^/]+(np|node)')


def install_node(envdir: str, language_version: str) -> str:
    windows = sys.platform in ('cygwin', 'win32')

    if sysconfig.get_config_var('HOST_GNU_TYPE') == 'x86_64-pc-linux-musl':
        domain = 'https://unofficial-builds.nodejs.org'
        suffix = 'linux-x64-musl.tar.gz'
    else:
        domain = 'https://nodejs.org'

        arch = ARCHITECTURES[platform.machine()]
        if windows:
            suffix = f'win-{arch}.zip'
        else:
            suffix = f'{platform.system().lower()}-{arch}.tar.gz'

    version = _node_version(domain, language_version)

    archive_name = f'node-{version}-{suffix}'
    with urlopen(
        urljoin(domain, f'download/release/{version}/{archive_name}'),
    ) as release:
        compressed = io.BytesIO(release.read())

    # TODO should i just extractall + rm the extra bits instead of partially
    #  extracting & renaming?

    if windows:
        # TODO this doesn't quite work
        def renamed_members(z: ZipFile) -> Generator[str, None, None]:
            prefix_len = len(f'{archive_name}/')
            for m in z.filelist:
                if WINDOWS_NODE_SUBDIRS.match(m.filename):
                    m.filename = m.filename[prefix_len:]
                    yield m.filename

        with ZipFile(compressed) as z:
            z.extractall(envdir, renamed_members(z))

        raise NotImplementedError('TODO')

    else:
        def rename_members_tf(tf: TarFile) -> Generator[TarInfo, None, None]:
            prefix_len = len(f'{archive_name}/')
            for m in tf.getmembers():
                if NIX_NODE_SUBDIRS.match(m.name):
                    m.path = m.path[prefix_len:]
                    yield m

        with TarFile.open(fileobj=compressed) as tf:
            tf.extractall(envdir, rename_members_tf(tf))  # type: ignore

        for file in ('npm', 'npx', 'node'):
            path = os.path.join(envdir, 'bin', file)
            os.chmod(path, os.stat(path).st_mode | 0o111)
        os.symlink('node', os.path.join(envdir, 'bin', 'nodejs'))

        return os.path.join(envdir, 'bin', 'npm')


def _node_version(domain: str, language_version: str) -> str:
    """Looks up the node.js version based on the configured
     version (uses the latest by default)"""
    if language_version == C.DEFAULT:
        # grab latest
        with urlopen(urljoin(domain, 'download/release/index.tab')) as index:
            _ = next(index)  # header
            version = next(index).split()[0].decode()
    else:
        version = f'v{language_version.replace("v", "")}'

    return version

import io
import os
import platform
import re
import sys
import sysconfig
from tarfile import TarFile
from tarfile import TarInfo
from typing import Generator
from urllib.parse import urljoin
from urllib.request import urlopen
from zipfile import ZipFile

import pre_commit.constants as C

ARCHITECTURES = {
    'x86': 'x86',  # Windows Vista 32
    'i686': 'x86',
    'x86_64': 'x64',  # Linux Ubuntu 64
    'amd64': 'x64',  # FreeBSD 64bits
    'AMD64': 'x64',  # Windows Server 2012 R2 (x64)
    'armv6l': 'armv6l',  # arm
    'armv7l': 'armv7l',
    'armv8l': 'armv7l',
    'aarch64': 'arm64',
    'arm64': 'arm64',
    'arm64/v8': 'arm64',
    'armv8': 'arm64',
    'armv8.4': 'arm64',
    'ppc64le': 'ppc64le',  # Power PC
    's390x': 's390x',  # IBM S390x
}

NIX_NODE_SUBDIRS = re.compile(r'node[^/]+/(bin|lib|include|share)')
WINDOWS_NODE_SUBDIRS = re.compile(r'node[^/]+(np|node)')


def install_node(envdir: str, language_version: str) -> str:
    windows = sys.platform in ('cygwin', 'win32')

    if sysconfig.get_config_var('HOST_GNU_TYPE') == 'x86_64-pc-linux-musl':
        domain = 'https://unofficial-builds.nodejs.org'
        suffix = 'linux-x64-musl.tar.gz'
    else:
        domain = 'https://nodejs.org'

        arch = ARCHITECTURES[platform.machine()]
        if windows:
            suffix = f'win-{arch}.zip'
        else:
            suffix = f'{platform.system().lower()}-{arch}.tar.gz'

    version = _node_version(domain, language_version)

    archive_name = f'node-{version}-{suffix}'
    with urlopen(
        urljoin(domain, f'download/release/{version}/{archive_name}'),
    ) as release:
        compressed = io.BytesIO(release.read())

    # TODO should i just extractall + rm the extra bits instead of partially
    #  extracting & renaming?

    if windows:
        # TODO this doesn't quite work
        def renamed_members(z: ZipFile) -> Generator[str, None, None]:
            prefix_len = len(f'{archive_name}/')
            for m in z.filelist:
                if WINDOWS_NODE_SUBDIRS.match(m.filename):
                    m.filename = m.filename[prefix_len:]
                    yield m.filename

        with ZipFile(compressed) as z:
            z.extractall(envdir, renamed_members(z))

        raise NotImplementedError('TODO')

    else:
        def rename_members_tf(tf: TarFile) -> Generator[TarInfo, None, None]:
            prefix_len = len(f'{archive_name}/')
            for m in tf.getmembers():
                if NIX_NODE_SUBDIRS.match(m.name):
                    m.path = m.path[prefix_len:]
                    yield m

        with TarFile.open(fileobj=compressed) as tf:
            tf.extractall(envdir, rename_members_tf(tf))  # type: ignore

        for file in ('npm', 'npx', 'node'):
            path = os.path.join(envdir, 'bin', file)
            os.chmod(path, os.stat(path).st_mode | 0o111)
        os.symlink('node', os.path.join(envdir, 'bin', 'nodejs'))

        return os.path.join(envdir, 'bin', 'npm')


def _node_version(domain: str, language_version: str) -> str:
    """Looks up the node.js version based on the configured
     version (uses the latest by default)"""
    if language_version == C.DEFAULT:
        # grab latest
        with urlopen(urljoin(domain, 'download/release/index.tab')) as index:
            _ = next(index)  # header
            version = next(index).split()[0].decode()
    else:
        version = f'v{language_version.replace("v", "")}'

    return version

# Windows Vista 32 # Linux Ubuntu 64 # FreeBSD 64bits # Windows Server 2012 R2 (x64) # arm # Power PC # IBM S390x # TODO should i just extractall + rm the extra bits instead of partially #  extracting & renaming? # TODO this doesn't quite work # type: ignore Looks up the node.js version based on the configured      version (uses the latest by default) # grab latest # header