import re
from typing import Optional, Tuple
from dataclasses import dataclass


@dataclass
class Version:
    """Semantic version"""
    major: int
    minor: int
    patch: int
    prerelease: Optional[str] = None
    build: Optional[str] = None
    
    @classmethod
    def parse(cls, version_string: str) -> 'Version':
        """Parse a semantic version string"""
        pattern = r'^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$'
        match = re.match(pattern, version_string)
        
        if not match:
            raise ValueError(f"Invalid semantic version: {version_string}")
        
        major, minor, patch, prerelease, build = match.groups()
        
        return cls(
            major=int(major),
            minor=int(minor),
            patch=int(patch),
            prerelease=prerelease,
            build=build
        )
    
    def __str__(self) -> str:
        """Convert to string"""
        version = f"{self.major}.{self.minor}.{self.patch}"
        if self.prerelease:
            version += f"-{self.prerelease}"
        if self.build:
            version += f"+{self.build}"
        return version
    
    def __eq__(self, other: 'Version') -> bool:
        """Check equality"""
        return (
            self.major == other.major and
            self.minor == other.minor and
            self.patch == other.patch and
            self.prerelease == other.prerelease
        )
    
    def __lt__(self, other: 'Version') -> bool:
        """Check if less than"""
        if self.major != other.major:
            return self.major < other.major
        if self.minor != other.minor:
            return self.minor < other.minor
        if self.patch != other.patch:
            return self.patch < other.patch
        
        if self.prerelease and not other.prerelease:
            return True
        if not self.prerelease and other.prerelease:
            return False
        if self.prerelease and other.prerelease:
            return self.prerelease < other.prerelease
        
        return False
    
    def __le__(self, other: 'Version') -> bool:
        return self < other or self == other
    
    def __gt__(self, other: 'Version') -> bool:
        return not self <= other
    
    def __ge__(self, other: 'Version') -> bool:
        return not self < other


class VersionConstraint:
    """Version constraint parser and matcher"""
    
    @staticmethod
    def matches(version: Version, constraint: str) -> bool:
        """Check if version matches constraint"""
        constraint = constraint.strip()
        
        if constraint == '*' or constraint == '':
            return True
        
        if constraint.startswith('>='):
            min_version = Version.parse(constraint[2:].strip())
            return version >= min_version
        
        if constraint.startswith('<='):
            max_version = Version.parse(constraint[2:].strip())
            return version <= max_version
        
        if constraint.startswith('>'):
            min_version = Version.parse(constraint[1:].strip())
            return version > min_version
        
        if constraint.startswith('<'):
            max_version = Version.parse(constraint[1:].strip())
            return version < max_version
        
        if constraint.startswith('=='):
            exact_version = Version.parse(constraint[2:].strip())
            return version == exact_version
        
        if constraint.startswith('^'):
            base_version = Version.parse(constraint[1:].strip())
            return (
                version.major == base_version.major and
                version >= base_version
            )
        
        if constraint.startswith('~'):
            base_version = Version.parse(constraint[1:].strip())
            return (
                version.major == base_version.major and
                version.minor == base_version.minor and
                version >= base_version
            )
        
        exact_version = Version.parse(constraint)
        return version == exact_version


class DependencyResolver:
    """Resolve plugin dependencies"""
    
    def __init__(self):
        self.installed = {}
    
    def register_installed(self, plugin_name: str, version: str):
        """Register an installed plugin"""
        self.installed[plugin_name] = Version.parse(version)
    
    def check_dependencies(self, dependencies: dict) -> Tuple[bool, list]:
        """Check if all dependencies are satisfied"""
        unmet = []
        
        for plugin_name, constraint in dependencies.items():
            if plugin_name not in self.installed:
                unmet.append(f"{plugin_name} (not installed)")
                continue
            
            installed_version = self.installed[plugin_name]
            if not VersionConstraint.matches(installed_version, constraint):
                unmet.append(
                    f"{plugin_name} {constraint} "
                    f"(installed: {installed_version})"
                )
        
        return len(unmet) == 0, unmet
