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

"""Anaconda Navigator conda interface."""
from __future__ import annotations

import functools
import json
import os
import typing

from navigator_updater.config import NAVIGATOR_LOCKFILE
from navigator_updater.external import filelock
from navigator_updater.utils.conda import exceptions
from navigator_updater.utils.conda import types as conda_types
from navigator_updater.utils.conda.core import get_conda_info
from navigator_updater.utils.conda.launch import run_process, launch_process, ProcessOutput


CondaSearchResult = typing.MutableMapping[str, typing.List[conda_types.CondaSearchPackageInfo]]


class CondaAPI:
    """An interface for interacting with conda."""

    @functools.cached_property
    def conda_info(self) -> conda_types.CondaInfoOutput:
        """Returns Conda information in a dictionary format."""
        info: conda_types.CondaInfoOutput | None = get_conda_info()
        if info is None:
            raise Exception('Conda info is not available!')  # pylint: disable=broad-exception-raised

        return info

    @property
    def root_prefix(self) -> str:
        """Returns the root prefix path where Conda is installed."""
        return self.conda_info.get('root_prefix', '')

    @property
    def conda_prefix(self) -> str:
        """Returns the Conda prefix path."""
        return self.conda_info.get('conda_prefix', self.root_prefix)

    @property
    def active_prefix(self) -> str:
        """Returns the active Conda environment prefix path."""
        return self.conda_info.get('active_prefix') or self.root_prefix

    @property
    def root_writable(self) -> bool:
        """Returns True if the root environment is writable; False otherwise."""
        return self.conda_info.get('root_writable', False)

    @functools.lru_cache
    def search(self, package: str) -> typing.Optional[CondaSearchResult]:
        """Searches for a package in conda repositories."""
        cmd: typing.Tuple[str, ...] = ('conda', 'search', package, '--json')
        process: ProcessOutput = run_process(cmd)

        result: typing.Union[CondaSearchResult, conda_types.CondaErrorOutput]
        try:
            result = json.loads(process.stdout)
        except (TypeError, ValueError):
            return None

        if 'error' not in result:
            return typing.cast(CondaSearchResult, result)

        raise exceptions.CondaError(error=typing.cast(conda_types.CondaErrorOutput, result))

    def install(self, package_spec: str) -> None:
        """Launch `conda install` subprocess."""
        cmd: typing.Tuple[str, ...] = ('conda', 'install', '--yes', '--json', '--force-reinstall', package_spec)

        with filelock.FileLock(NAVIGATOR_LOCKFILE):  # type: ignore
            launch_process(cmd)

    def linked(self, prefix: str | None = None) -> typing.Set[str]:
        """Returns a set of canonical names of linked packages in the specified Conda environment."""
        prefix = self.active_prefix if prefix is None else prefix

        if not os.path.isdir(prefix):
            return set()

        packages: typing.Set[str] = set()
        meta_dir: str = os.path.join(prefix, 'conda-meta')

        if os.path.isdir(meta_dir):
            meta_files: typing.Set[str] = set(fname for fname in os.listdir(meta_dir) if fname.endswith('.json'))
            packages = set(fname[:-5] for fname in meta_files)

        return packages
