# -*- coding: utf-8 -*-

"""Installable VS Code application description."""

__all__ = ['VSCodeApp']

import datetime
import json
import os
import typing
from anaconda_navigator.api.external_apps.base import BaseInstallableApp
from anaconda_navigator.api.conda_api import CondaAPI, get_pyscript, get_pyexec
from anaconda_navigator import config as navigator_config
from anaconda_navigator.static import images
from anaconda_navigator.utils.conda import launch as conda_launch_utils
from anaconda_navigator.utils.logs import logger
from . import detectors

if typing.TYPE_CHECKING:
    import typing_extensions
    from anaconda_navigator.api import process
    from anaconda_navigator.config import user as user_config


def check_version(
        parent: typing.Iterator[detectors.DetectedApplication],
) -> typing.Iterator[detectors.DetectedApplication]:
    """Detect version of the VS Code application."""
    application: detectors.DetectedApplication
    for application in parent:
        if not application.executable:
            continue

        stdout: str
        stdout, _, _ = conda_launch_utils.run_process([application.executable, '--version'])
        if stdout:
            yield application.replace(version=stdout.splitlines()[0])
        else:
            yield application.replace(version='unknown')


class VSCodeApp(BaseInstallableApp):
    """Microsoft VS Code application."""

    def __init__(self, process_api: 'process.WorkerManager', config: 'user_config.UserConfig') -> None:
        """Initialize new :class:`~VSCodeApp` instance."""
        detector: 'typing_extensions.Final[detectors.Source]' = detectors.Group(
            detectors.Group(
                detectors.CheckConfiguredRoots('vscode_path', configuration=config),
                detectors.mac_only(),

                detectors.check_known_mac_roots('Visual Studio Code.app'),

                detectors.AppendExecutable(os.path.join('Contents', 'Resources', 'app', 'bin', 'code')),
            ),
            detectors.Group(
                detectors.CheckConfiguredRoots('vscode_path', configuration=config),
                detectors.linux_only(),

                detectors.CheckKnownRoots(
                    detectors.join(detectors.Linux.root, 'usr', 'share', 'code'),
                    detectors.join(detectors.Linux.root, 'snap', 'code', 'current', 'usr', 'share', 'code'),
                    detectors.join(
                        detectors.Linux.root, 'var', 'lib', 'snapd', 'snap', 'code', 'current', 'usr', 'share', 'code',
                    ),
                ),

                detectors.AppendExecutable(os.path.join('bin', 'code')),
            ),
            detectors.Group(
                detectors.CheckConfiguredRoots('vscode_path', configuration=config),
                detectors.win_only(),

                detectors.CheckKnownRoots(
                    detectors.join(detectors.Win.program_files_x86, 'Microsoft VS Code'),
                    detectors.join(detectors.Win.program_files_x64, 'Microsoft VS Code'),
                    detectors.join(detectors.Win.local_app_data, 'Programs', 'Microsoft VS Code'),
                ),

                detectors.AppendExecutable(os.path.join('bin', 'code.cmd'))
            ),

            check_version,
        )

        super().__init__(
            app_name='vscode',
            display_name='VS Code',
            description=(
                'Streamlined code editor with support for development operations like debugging, task running and '
                'version control.'
            ),
            image_path=images.VSCODE_ICON_1024_PATH,
            detector=detector,
            is_available=True,
            process_api=process_api,
            config=config,
            extra_arguments=('--user-data-dir', os.path.join(navigator_config.CONF_PATH, 'Code'))
        )

    def install_extensions(self) -> 'process.ProcessWorker':
        """Install app extensions."""
        if self.executable is None:
            return self._process_api.create_process_worker(['python', '--version'])

        cmd: typing.Sequence[str] = [
            self.executable,
            '--install-extension',
            'ms-python.anaconda-extension-pack',
            # 'ms-python-anaconda-extension',
            # 'ms-python.python',
        ]
        return self._process_api.create_process_worker(cmd)

    def update_config(self, prefix: str) -> None:
        """Update user config to use selected Python prefix interpreter."""
        try:
            _config_dir: str = os.path.join(navigator_config.CONF_PATH, 'Code', 'User')
            _config: str = os.path.join(_config_dir, 'settings.json')

            try:
                os.makedirs(_config_dir, exist_ok=True)
            except OSError as exception:
                logger.error(exception)
                return

            stream: typing.TextIO
            config_data: typing.Dict[str, typing.Any]
            if os.path.isfile(_config):
                try:
                    with open(_config, 'rt', encoding='utf-8') as stream:
                        data = stream.read()
                    self.create_config_backup(data)

                    config_data = json.loads(data)
                except Exception:  # pylint: disable=broad-except
                    return
            else:
                config_data = {}

            config_data.update({
                'python.terminal.activateEnvironment': True,
                'python.pythonPath': get_pyexec(prefix),
                'python.defaultInterpreterPath': get_pyexec(prefix),
                'python.condaPath': get_pyscript(CondaAPI().ROOT_PREFIX, 'conda'),
            })
            with open(_config, 'wt', encoding='utf-8') as stream:
                json.dump(config_data, stream, sort_keys=True, indent=4)

        except Exception as exception:  # pylint: disable=broad-except
            logger.error(exception)
            return

    @staticmethod
    def create_config_backup(data: str) -> None:
        """
        Create a backup copy of the app configuration file `data`.

        Leave only the last 10 backups.
        """
        date: str = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
        _config_dir: str = os.path.join(navigator_config.CONF_PATH, 'Code', 'User')
        _config_bck: str = os.path.join(_config_dir, f'bck.{date}.navigator.settings.json')

        # Make the backup
        stream: typing.TextIO
        with open(_config_bck, 'wt', encoding='utf-8') as stream:
            stream.write(data)

        # Only keep the latest 10 backups
        files: typing.List[str] = [
            os.path.join(_config_dir, item)
            for item in os.listdir(_config_dir)
            if item.startswith('bck.') and item.endswith('.navigator.settings.json')
        ]
        path: str
        for path in sorted(files, reverse=True)[10:]:
            try:
                os.remove(path)
            except OSError:
                pass
