from __future__ import annotations

import importlib.metadata as im
import itertools
import sys
from collections import ChainMap
from collections.abc import Iterable, Iterator, Mapping
from pathlib import Path

from pdm.utils import normalize_name

default_context = im.DistributionFinder.Context()


class EgglinkFinder(im.DistributionFinder):
    @classmethod
    def find_distributions(cls, context: im.DistributionFinder.Context = default_context) -> Iterable[im.Distribution]:
        found_links = cls._search_paths(context.name, context.path)
        # For Py3.7 compatibility, handle both classmethod and instance method
        meta_finder = im.MetadataPathFinder()
        for link in found_links:
            name = link.stem
            with link.open("rb") as file_link:
                link_pointer = Path(file_link.readline().decode().strip())
            dist = next(
                iter(
                    meta_finder.find_distributions(im.DistributionFinder.Context(name=name, path=[str(link_pointer)]))
                ),
                None,
            )
            if not dist:
                continue
            dist.link_file = link.absolute()  # type: ignore[attr-defined]
            yield dist

    @classmethod
    def _search_paths(cls, name: str | None, paths: list[str]) -> Iterable[Path]:
        for path in paths:
            if name:
                if Path(path).joinpath(f"{name}.egg-link").is_file():
                    yield Path(path).joinpath(f"{name}.egg-link")
            else:
                yield from Path(path).glob("*.egg-link")


def distributions(path: list[str]) -> Iterable[im.Distribution]:
    """Find distributions in the paths. Similar to `importlib.metadata`'s
    implementation but with the ability to discover egg-links.
    """
    context = im.DistributionFinder.Context(path=path)
    resolvers = itertools.chain(
        filter(
            None,
            (getattr(finder, "find_distributions", None) for finder in sys.meta_path),
        ),
        (EgglinkFinder.find_distributions,),
    )
    return itertools.chain.from_iterable(resolver(context) for resolver in resolvers)


class WorkingSet(Mapping[str, im.Distribution]):
    """A dictionary of currently installed distributions"""

    def __init__(self, paths: list[str] | None = None, shared_paths: list[str] | None = None) -> None:
        if paths is None:
            paths = sys.path
        if shared_paths is None:
            shared_paths = []
        self._dist_map = {
            normalize_name(dist.metadata["Name"]): dist
            for dist in distributions(path=list(dict.fromkeys(paths)))
            if dist.metadata.get("Name")
        }
        self._shared_map = {
            normalize_name(dist.metadata["Name"]): dist
            for dist in distributions(path=list(dict.fromkeys(shared_paths)))
            if dist.metadata.get("Name")
        }
        self._iter_map = ChainMap(self._dist_map, self._shared_map)

    def __getitem__(self, key: str) -> im.Distribution:
        return self._iter_map[key]

    def is_owned(self, key: str) -> bool:
        return key in self._dist_map

    def __len__(self) -> int:
        return len(self._iter_map)

    def __iter__(self) -> Iterator[str]:
        return iter(self._iter_map)

    def __repr__(self) -> str:
        return repr(self._iter_map)
