import copy
import re

from contextlib import
 contextmanager
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

from poetry.core.packages.specification import PackageSpecification
from poetry.core.packages.utils.utils import create_nested_marker
from poetry.core.semver.helpers import parse_constraint
from poetry.cor
e.version.markers import parse_marker


if TYPE_CHECKING:
    from poetry.core.packages.dependency_group import DependencyGroup
    from poetry.core.packages.types import DependencyTypes
    from poetry.core.semver.version import Version
    from poetry.core.semver.version_constraint import VersionConstraint
    from poetry.core.spdx.license import License
    from poetry.core.version.markers import BaseMarker

AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()&]+)(?: <(?P<email>.+?)>)?$")


class Package(PackageSpecification):

    AVAILABLE_PYTHONS = {
        "2",
        "2.7",
        "3",
        "3.4",
        "3.5",
        "3.6",
        "3.7",
        "3.8",
        "3.9",
        "3.10",
    }

    def __init__(
        self,
        name: str,
        version: Union[str, "Version"],
        pretty_version: Optional[str] = None,
        source_type: Optional[str] = None,
        source_url: Optional[str] = None,
        source_reference: Optional[str] = None,
        source_resolved_reference: Optional[str] = None,
        source_subdirectory: Optional[str] = None,
        features: Optional[List[str]] = None,
        develop: bool = False,
    ) -> None:
        """
        Creates a new in memory package.
        """
        from poetry.core.semver.version import Version
        from poetry.core.version.markers import AnyMarker

        super().__init__(
            name,
            source_type=source_type,
            source_url=source_url,
            source_reference=source_reference,
            source_resolved_reference=source_resolved_reference,
            source_subdirectory=source_subdirectory,
            features=features,
        )

        if not isinstance(version, Version):
            self._version = Version.parse(version)
            self._pretty_version = pretty_version or version
        else:
            self._version = version
            self._pretty_version = pretty_version or self._version.text

        self.description = ""

        self._authors = []
        self._maintainers = []

        self.homepage = None
        self.repository_url = None
        self.documentation_url = None
        self.keywords = []
        self._license = None
        self.readmes: Tuple[Path, ...] = ()

        self.extras = {}
        self.requires_extras = []

        self._dependency_groups: Dict[str, "DependencyGroup"] = {}

        # For compatibility with previous version, we keep the category
        self.category = "main"
        self.files = []
        self.optional = False

        self.classifiers = []

        self._python_versions = "*"
        self._python_constraint = parse_constraint("*")
        self._python_marker = AnyMarker()

        self.platform = None
        self.marker = AnyMarker()

        self.root_dir: Optional[Path] = None

        self.develop = develop

    @property
    def name(self) -> str:
        return self._name

    @property
    def pretty_name(self) -> str:
        return self._pretty_name

    @property
    def version(self) -> "Version":
        return self._version

    @property
    def pretty_version(self) -> str:
        return self._pretty_version

    @property
    def unique_name(self) -> str:
        if self.is_root():
            return self._name

        return self.complete_name + "-" + self._version.text

    @property
    def pretty_string(self) -> str:
        return self.pretty_name + " " + self.pretty_version

    @property
    def full_pretty_version(self) -> str:
        if self.source_type in ["file", "directory", "url"]:
            return f"{self._pretty_version} {self.source_url}"

        if self.source_type not in ["hg", "git"]:
            return self._pretty_version

        if self.source_resolved_reference and len(self.source_resolved_reference) == 40:
            ref = self.source_resolved_reference[0:7]
            return f"{self._pretty_version} {ref}"

        # if source reference is a sha1 hash -- truncate
        if len(self.source_reference) == 40:
            return f"{self._pretty_version} {self.source_reference[0:7]}"

        ref = self._source_resolved_reference or self._source_reference
        return f"{self._pretty_version} {ref}"

    @property
    def authors(self) -> List[str]:
        return self._authors

    @property
    def author_name(self) -> str:
        return self._get_author()["name"]

    @property
    def author_email(self) -> str:
        return self._get_author()["email"]

    @property
    def maintainers(self) -> List[str]:
        return self._maintainers

    @property
    def maintainer_name(self) -> str:
        return self._get_maintainer()["name"]

    @property
    def maintainer_email(self) -> str:
        return self._get_maintainer()["email"]

    @property
    def requires(self) -> List["DependencyTypes"]:
        """
        Returns the default dependencies
        """
        if not self._dependency_groups or "default" not in self._dependency_groups:
            return []

        return self._dependency_groups["default"].dependencies

    @property
    def all_requires(
        self,
    ) -> List[Union["DependencyTypes"]]:
        """
        Returns the default dependencies and group dependencies.
        """
        return self.requires + [
            dependency
            for group in self._dependency_groups.values()
            for dependency in group.dependencies
            if group.name != "default"
        ]

    def _get_author(self) -> Dict[str, Optional[str]]:
        if not self._authors:
            return {"name": None, "email": None}

        m = AUTHOR_REGEX.match(self._authors[0])

        if m is None:
            raise ValueError(
                "Invalid author string. Must be in the format: "
                "John Smith <john@example.com>"
            )

        name = m.group("name")
        email = m.group("email")

        return {"name": name, "email": email}

    def _get_maintainer(self) -> Dict[str, Optional[str]]:
        if not self._maintainers:
            return {"name": None, "email": None}

        m = AUTHOR_REGEX.match(self._maintainers[0])

        if m is None:
            raise ValueError(
                "Invalid maintainer string. Must be in the format: "
                "John Smith <john@example.com>"
            )

        name = m.group("name")
        email = m.group("email")

        return {"name": name, "email": email}

    @property
    def python_versions(self) -> str:
        return self._python_versions

    @python_versions.setter
    def python_versions(self, value: str) -> None:
        self._python_versions = value
        self._python_constraint = parse_constraint(value)
        self._python_marker = parse_marker(
            create_nested_marker("python_version", self._python_constraint)
        )

    @property
    def python_constraint(self) -> "VersionConstraint":
        return self._python_constraint

    @property
    def python_marker(self) -> "BaseMarker":
        return self._python_marker

    @property
    def license(self) -> Optional["License"]:
        return self._license

    @license.setter
    def license(self, value: Optional[Union[str, "License"]]) -> None:
        from poetry.core.spdx.helpers import license_by_id
        from poetry.core.spdx.license import License

        if value is None or isinstance(value, License):
            self._license = value
        else:
            self._license = license_by_id(value)

    @property
    def all_classifiers(self) -> List[str]:
        from poetry.core.semver.version import Version

        classifiers = copy.copy(self.classifiers)

        # Automatically set python classifiers
        if self.python_version