from contextlib import suppress
from functools import cached_property
from typing import Any
from urllib.parse import urljoin

import requests
from packaging.utils import NormalizedName
from poetry.config.config import Config
from poetry.core.constraints.version import Version, VersionConstraint
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.repositories.exceptions import PackageNotFoundError
from poetry.repositories.http_repository import HTTPRepository
from poetry.repositories.link_sources.base import SimpleRepositoryRootPage
from poetry.repositories.link_sources.html import SimpleRepositoryHTMLRootPage
from poetry.repositories.link_sources.json import SimpleRepositoryJsonRootPage


class DevPiRepository(HTTPRepository):
    def __init__(
            self,
            url: str,
            *,
            config: Config | None = None,
            disable_cache: bool = False,
            pool_size: int = requests.adapters.DEFAULT_POOLSIZE,
            fallback: bool = True,
    ) -> None:
        super().__init__(
            "DevPi",
            urljoin(url, "root/pypi/+simple"),
            config=config,
            disable_cache=disable_cache,
            pool_size=pool_size,
            )

        self._base_url = url
        self._fallback = fallback
        self._status_url = urljoin(url, "+status")

    def is_available(self) -> bool:
        try:
            return requests.get(self._status_url, timeout=3.0).status_code == 200
        except requests.exceptions.RequestException:
            return False

    def find_links_for_package(self, package: Package) -> list[Link]:
        try:
            page = self.get_page(package.name)
        except PackageNotFoundError:
            return []

        return list(page.links_for_version(package.name, package.version))

    def _find_packages(
            self, name: NormalizedName, constraint: VersionConstraint
    ) -> list[Package]:
        """
        Find packages on the remote server.
        """
        try:
            page = self.get_page(name)
        except PackageNotFoundError:
            self._log(f"No packages found for {name}", level="debug")
            return []

        versions = [
            (version, page.yanked(name, version))
            for version in page.versions(name)
            if constraint.allows(version)
        ]

        return [
            Package(
                name,
                version,
                yanked=yanked,
            )
            for version, yanked in versions
        ]

    def _get_release_info(self, name: NormalizedName, version: Version) -> dict[str, Any]:
        from poetry.inspection.info import PackageInfo
        page = self.get_page(name)

        links = list(page.links_for_version(name, version))
        yanked = page.yanked(name, version)

        return self._links_to_data(
            links,
            PackageInfo(
                name=name,
                version=version.text,
                summary="",
                requires_dist=[],
                requires_python=None,
                files=[],
                yanked=yanked,
                cache_version=str(self.CACHE_VERSION),
            ),
        )

    @cached_property
    def root_page(self) -> SimpleRepositoryRootPage:
        if not (
                response := self._get_response("/", headers=self._get_prefer_json_header())
        ):
            self._log(
                f"Unable to retrieve package listing from package source {self.name}",
                level="error",
            )
            return SimpleRepositoryRootPage()

        if self._is_json_response(response):
            return SimpleRepositoryJsonRootPage(response.json())

        return SimpleRepositoryHTMLRootPage(response.text)

    def search(self, query: str | list[str]) -> list[Package]:
        results: list[Package] = []

        for candidate in self.root_page.search(query):
            with suppress(PackageNotFoundError):
                page = self.get_page(candidate)

                for package in page.packages:
                    results.append(package)

        return results
