import re
from typing import Dict, List, Optional, Tuple, Any, Pattern, Set
from collections import defaultdict
from email.utils import parseaddr
from urllib.parse import urlparse
import packaging.version


# META PARSER


class MetaText:
    """
    A class for extracting and analyzing metadata from Python packages.

    This class is designed to process metadata text in METADATA 2.1 format
    as found in PKG-INFO files or when using pip show.
    """

    # Pre-compiled regex patterns
    _EMAIL_PATTERN: Pattern = re.compile(r"(.+?)<(.+?)>")
    _URL_PATTERN: Pattern = re.compile(r"([^,]+),\s*(.+)")
    _COPYRIGHT_PATTERN: Pattern = re.compile(
        r"Copyright\s*\([cC]\)\s*(.*?)(?:\n\n|$)", re.DOTALL
    )
    _VERSION_PATTERN: Pattern = re.compile(
        r"^(\d+\.\d+(?:\.\d+)*)(?:[\.\-\_a-zA-Z0-9]*)$"
    )
    _MULTILINE_CONTINUATION: Pattern = re.compile(r"^\s+")

    def __init__(self, metadata_text: str):
        self.metadata_text = metadata_text.strip()
        self.lines = self.metadata_text.split("\n")
        self._cache: Dict[str, Any] = {}
        self._multiline_data: Dict[str, List[str]] = defaultdict(list)
        self._parse_multiline_data()

    def _parse_multiline_data(self) -> None:
        """Parse and cache all multiline data efficiently."""
        current_key = None
        current_lines = []

        for line in self.lines:
            if ":" in line and not line.startswith(" "):
                # Save previous multiline data
                if current_key and current_lines:
                    self._multiline_data[current_key] = current_lines

                # Start new section
                current_key = line.split(":", 1)[0].strip()
                current_lines = [line.split(":", 1)[1].strip()]
            elif current_key and self._MULTILINE_CONTINUATION.match(line):
                current_lines.append(line.strip())

        # Save the last section
        if current_key and current_lines:
            self._multiline_data[current_key] = current_lines

    def _get_cached_or_compute(self, key: str, compute_func) -> Any:
        """Helper method to cache computed results."""
        if key not in self._cache:
            self._cache[key] = compute_func()
        return self._cache[key]

    def _find_line_value(self, prefix: str) -> str:
        """Find and extract value from a line starting with prefix."""
        for line in self.lines:
            if line.startswith(prefix):
                return line.split(":", 1)[1].strip()
        return ""

    def _get_multiline_value(self, key: str) -> str:
        """Get multiline value from pre-parsed data."""
        if key in self._multiline_data:
            return " ".join(self._multiline_data[key]).strip()
        return ""

    def _collect_lines_with_prefix(self, prefix: str) -> List[str]:
        """Collect all lines starting with a specific prefix."""
        return [
            line.split(":", 1)[1].strip()
            for line in self.lines
            if line.startswith(prefix)
        ]

    @property
    def description(self) -> str:
        """Extract full description with proper multiline handling."""
        return self._get_cached_or_compute(
            "description", lambda: self._get_multiline_value("Description")
        )

    @property
    def license_full(self) -> str:
        """Extract complete license text with structure."""
        license_lines = self._multiline_data.get("License", [])
        if not license_lines:
            return ""

        # Extract license structure
        license_text = " ".join(license_lines)

        # Try to identify license type from structure
        if "MIT" in license_text:
            return f"MIT License\n\n{license_text}"
        elif "BSD" in license_text:
            return f"BSD License\n\n{license_text}"
        elif "Apache" in license_text:
            return f"Apache License\n\n{license_text}"

        return license_text

    @property
    def classifier_categories(self) -> Dict[str, List[str]]:
        """Organize classifiers by category."""

        def compute_classifier_categories():
            categories = defaultdict(list)
            for classifier in self.classifiers:
                parts = classifier.split(" :: ")
                if len(parts) >= 2:
                    category = parts[0]
                    categories[category].append(classifier)
            return dict(categories)

        return self._get_cached_or_compute(
            "classifier_categories", compute_classifier_categories
        )

    @property
    def deps_with_extras(self) -> Dict[str, List[str]]:
        """Get dependencies grouped by their extras requirements."""

        def compute_deps_with_extras():
            deps_by_extra = defaultdict(list)
            required_deps = []

            for line in self.lines:
                if line.startswith("Requires-Dist:"):
                    dep_line = line.split(":", 1)[1].strip()

                    if "extra ==" in dep_line:
                        parts = dep_line.split("extra ==", 1)
                        dep = parts[0].strip().rstrip(";").strip()
                        extra = parts[1].strip().strip("\"'").strip()
                        deps_by_extra[extra].append(dep)
                    else:
                        required_deps.append(dep_line)

            return {"required": required_deps, "optional": dict(deps_by_extra)}

        return self._get_cached_or_compute("deps_with_extras", compute_deps_with_extras)

    @property
    def project_relationships(self) -> Dict[str, Any]:
        """Analyze relationships between project URLs and other data."""
        urls = self.project_urls
        relationships = {
            "homepage": urls.get("Homepage", ""),
            "documentation": urls.get("Documentation", ""),
            "repository": urls.get("Repository", ""),
            "changelog": urls.get("Changelog", ""),
            "tracker": urls.get("Tracker", ""),
            "related_projects": {},
        }

        # Link URLs with classifiers
        for classifier in self.classifiers:
            if "Framework :: " in classifier:
                relationships["related_projects"]["framework"] = classifier.split("::")[
                    -1
                ].strip()

        return relationships

    @property
    def structured_author_info(self) -> Dict[str, Any]:
        """Extract structured author information."""
        author_email = self.author_email
        maintainers = []

        # Extract maintainers from classifiers
        for classifier in self.classifiers:
            if "Maintainer :: " in classifier:
                maintainers.append(classifier.split("::")[-1].strip())

        return {
            "author": author_email["author"],
            "email": author_email["email"],
            "maintainers": maintainers,
            "has_contact": bool(author_email["email"]),
            "has_maintainers": bool(maintainers),
        }

    def validate_email(self, email: str) -> bool:
        """Validate email format."""
        if not email:
            return False
        return "@" in email and "." in email.split("@")[-1]

    def validate_url(self, url: str) -> bool:
        """Validate URL format."""
        try:
            result = urlparse(url)
            return all([result.scheme, result.netloc])
        except:
            return False

    def validate_version(self, version: str) -> bool:
        """Validate version format."""
        if not version:
            return False
        try:
            packaging.version.parse(version)
            return True
        except packaging.version.InvalidVersion:
            return self._VERSION_PATTERN.match(version) is not None

    @property
    def validation_report(self) -> Dict[str, Any]:
        """Generate validation report for metadata."""
        report = {
            "email_valid": self.validate_email(self.author_email["email"]),
            "urls_valid": all(
                self.validate_url(url) for url in self.project_urls.values()
            ),
            "version_valid": self.validate_version(self.version),
            "missing_fields": [],
        }

        # Check for missing required fields
        required_fields = ["Name", "Version", "Summary"]
        for field in required_fields:
            if not self._find_line_value(field + ":"):
                report["missing_fields"].append(field.lower())

        return report

    def clear_cache(self) -> None:
        """Clear the internal cache."""
        self._cache.clear()

    def precompute_all(self) -> None:
        """Precompute all properties for faster access."""
        _ = self.name
        _ = self.version
        _ = self.summary
        _ = self.author_email
        _ = self.license
        _ = self.classifiers
        _ = self.project_urls
        _ = self.pyrequirement
        _ = self.deps
        _ = self.content_type
        _ = self.copyright
        _ = self.extras
        _ = self.metadata_version

    @property
    def classifier_analysis(self) -> Dict[str, Any]:
        """Deep analysis of classifiers."""
        categories = self.classifier_categories
        analysis = {
            "total_classifiers": len(self.classifiers),
            "categories_count": len(categories),
            "development_status": [],
            "python_versions": [],
            "operating_systems": [],
            "frameworks": [],
        }

        for classifier in self.classifiers:
            if "Development Status ::" in classifier:
                analysis["development_status"].append(classifier)
            elif "Programming Language :: Python ::" in classifier:
                analysis["python_versions"].append(classifier)
            elif "Operating System ::" in classifier:
                analysis["operating_systems"].append(classifier)
            elif "Framework ::" in classifier:
                analysis["frameworks"].append(classifier)

        return analysis

    @property
    def dependency_analysis(self) -> Dict[str, Any]:
        """Analyze dependency patterns."""
        deps = self.deps
        analysis = {
            "total_required_deps": len(deps["required"]),
            "total_optional_deps": sum(len(v) for v in deps["optional"].values()),
            "extras_count": len(deps["optional"]),
            "has_conditional_deps": any(";" in dep for dep in deps["required"]),
            "common_prefixes": defaultdict(int),
        }

        # Analyze dependency naming patterns
        for dep in deps["required"]:
            if ">=" in dep:
                analysis["common_prefixes"]["version_gte"] += 1
            elif "==" in dep:
                analysis["common_prefixes"]["version_eq"] += 1

        return analysis

    def _robust_multiline_parser(self, key: str) -> List[str]:
        """Robust parsing for multiline values with various formats."""
        lines = []
        in_section = False

        for line in self.lines:
            if line.startswith(key + ":"):
                lines.append(line.split(":", 1)[1].strip())
                in_section = True
            elif in_section:
                if line.strip() and not line.startswith(" ") and ":" in line:
                    break
                if line.strip():
                    lines.append(line.strip())

        return lines

    def search_classifiers(self, pattern: str) -> List[str]:
        """Search classifiers by pattern."""
        return [cls for cls in self.classifiers if pattern.lower() in cls.lower()]

    def filter_dependencies(self, pattern: str = None) -> List[str]:
        """Filter dependencies by pattern."""
        deps = self.deps["required"] + [
            dep for extra_deps in self.deps["optional"].values() for dep in extra_deps
        ]
        if pattern:
            return [dep for dep in deps if pattern.lower() in dep.lower()]
        return deps

    def get_extra_dependencies(self, extra_name: str) -> List[str]:
        """Get dependencies for a specific extra."""
        return self.deps["optional"].get(extra_name, [])

    def to_dict(self, include_analysis: bool = False) -> Dict[str, Any]:
        """Convert metadata to dictionary with optional analysis."""
        base_data = {
            "name": self.name,
            "version": self.version,
            "summary": self.summary,
            "author_email": self.author_email,
            "license": self.license,
            "classifiers": self.classifiers,
            "project_urls": self.project_urls,
            "pyrequirement": self.pyrequirement,
            "deps": self.deps,
            "content_type": self.content_type,
            "copyright": self.copyright,
            "extras": self.extras,
            "metadata_version": self.metadata_version,
        }

        if include_analysis:
            base_data.update(
                {
                    "classifier_analysis": self.classifier_analysis,
                    "dependency_analysis": self.dependency_analysis,
                    "validation_report": self.validation_report,
                }
            )

        return base_data

    # الخصائص الأصلية (محسنة)
    @property
    def name(self) -> str:
        return self._find_line_value("Name:")

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

    @property
    def summary(self) -> str:
        return self._find_line_value("Summary:")

    @property
    def license(self) -> str:
        return self._get_multiline_value("License")

    @property
    def classifiers(self) -> List[str]:
        return self._collect_lines_with_prefix("Classifier:")

    @property
    def pyrequirement(self) -> str:
        return self._find_line_value("Requires-Python:")

    @property
    def content_type(self) -> str:
        return self._find_line_value("Description-Content-Type:")

    @property
    def extras(self) -> List[str]:
        return [
            line.split(":", 1)[1].strip().strip("\"'")
            for line in self.lines
            if line.startswith("Provides-Extra:")
        ]

    @property
    def metadata_version(self) -> str:
        return self._find_line_value("Metadata-Version:")

    @property
    def author_email(self) -> Dict[str, str]:
        def compute():
            value = self._find_line_value("Author-Email:")
            if not value:
                return {"author": "", "email": ""}
            match = self._EMAIL_PATTERN.match(value)
            return (
                {"author": match.group(1).strip(), "email": match.group(2).strip()}
                if match
                else {"author": value, "email": ""}
            )

        return self._get_cached_or_compute("author_email", compute)

    @property
    def project_urls(self) -> Dict[str, str]:
        def compute():
            urls = {}
            for line in self.lines:
                if line.startswith("Project-URL:"):
                    value = line.split(":", 1)[1].strip()
                    match = self._URL_PATTERN.match(value)
                    if match:
                        urls[match.group(1).strip()] = match.group(2).strip()
            return urls

        return self._get_cached_or_compute("project_urls", compute)

    @property
    def deps(self) -> Dict[str, Any]:
        def compute():
            dependencies = {"required": [], "optional": defaultdict(list)}
            for line in self.lines:
                if line.startswith("Requires-Dist:"):
                    dep_line = line.split(":", 1)[1].strip()
                    if "extra ==" in dep_line:
                        parts = dep_line.split("extra ==", 1)
                        dep, extra = (
                            parts[0].strip().rstrip(";").strip(),
                            parts[1].strip().strip("\"'").strip(),
                        )
                        dependencies["optional"][extra].append(dep)
                    else:
                        dependencies["required"].append(dep_line)
                elif line.startswith("Provides-Extra:"):
                    extra = line.split(":", 1)[1].strip().strip("\"'")
                    if extra not in dependencies["optional"]:
                        dependencies["optional"][extra] = []
            dependencies["optional"] = dict(dependencies["optional"])
            return dependencies

        return self._get_cached_or_compute("deps", compute)

    @property
    def copyright(self) -> str:
        def compute():
            license_text = self.license
            match = self._COPYRIGHT_PATTERN.search(license_text)
            return match.group(1).strip() if match else ""

        return self._get_cached_or_compute("copyright", compute)
