"""CVE database integration using OSV (Open Source Vulnerabilities).

Checks dependencies against known vulnerabilities without requiring API keys.
Uses the OSV.dev API which covers PyPI, npm, and other ecosystems.
"""
import json
import re
import urllib.request
import urllib.error
from dataclasses import dataclass
from typing import List, Optional, Dict, Tuple
from pathlib import Path

from .patterns import Finding, Severity


OSV_API_URL = "https://api.osv.dev/v1/query"
OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch"

# Cache for OSV responses (in-memory, per-session)
_osv_cache: Dict[str, List[dict]] = {}


@dataclass
class PackageVersion:
    """A package with optional version constraint."""
    name: str
    version: Optional[str] = None
    ecosystem: str = "PyPI"  # PyPI, npm, Go, etc.
    
    def to_osv_query(self) -> dict:
        """Convert to OSV API query format."""
        query = {
            "package": {
                "name": self.name,
                "ecosystem": self.ecosystem,
            }
        }
        if self.version:
            query["version"] = self.version
        return query


def parse_requirements_txt(content: str) -> List[PackageVersion]:
    """Parse requirements.txt format."""
    packages = []
    
    for line in content.split('\n'):
        line = line.strip()
        
        # Skip comments and empty lines
        if not line or line.startswith('#') or line.startswith('-'):
            continue
        
        # Skip URLs and paths
        if line.startswith(('http://', 'https://', 'git+', '/', '.')):
            continue
        
        # Parse package==version, package>=version, etc.
        match = re.match(r'^([a-zA-Z0-9_-]+)\s*(?:[=<>!~]+\s*([0-9][a-zA-Z0-9._-]*))?', line)
        if match:
            name = match.group(1).lower()
            version = match.group(2)
            packages.append(PackageVersion(name=name, version=version, ecosystem="PyPI"))
    
    return packages


def parse_package_json(content: str) -> List[PackageVersion]:
    """Parse package.json dependencies."""
    packages = []
    
    try:
        data = json.loads(content)
    except json.JSONDecodeError:
        return packages
    
    for dep_key in ('dependencies', 'devDependencies', 'peerDependencies'):
        deps = data.get(dep_key, {})
        for name, version_spec in deps.items():
            # Extract version number from spec like "^1.2.3", "~1.2.3", ">=1.0.0"
            version_match = re.search(r'(\d+\.\d+\.\d+)', version_spec)
            version = version_match.group(1) if version_match else None
            packages.append(PackageVersion(name=name, version=version, ecosystem="npm"))
    
    return packages


def parse_pyproject_toml(content: str) -> List[PackageVersion]:
    """Parse pyproject.toml dependencies (simple parsing, not full TOML)."""
    packages = []
    
    in_deps = False
    for line in content.split('\n'):
        line = line.strip()
        
        if line.startswith('[') and 'dependencies' in line.lower():
            in_deps = True
            continue
        elif line.startswith('['):
            in_deps = False
            continue
        
        if in_deps and '=' in line:
            # Handle: package = ">=1.0"
            match = re.match(r'^([a-zA-Z0-9_-]+)\s*=\s*["\']?([^"\']+)?', line)
            if match:
                name = match.group(1).lower()
                version_spec = match.group(2) or ''
                version_match = re.search(r'(\d+\.\d+\.?\d*)', version_spec)
                version = version_match.group(1) if version_match else None
                packages.append(PackageVersion(name=name, version=version, ecosystem="PyPI"))
        elif in_deps and line and not line.startswith('#'):
            # Handle list format: "package>=1.0",
            match = re.match(r'^["\']?([a-zA-Z0-9_-]+)', line)
            if match:
                name = match.group(1).lower()
                version_match = re.search(r'(\d+\.\d+\.?\d*)', line)
                version = version_match.group(1) if version_match else None
                packages.append(PackageVersion(name=name, version=version, ecosystem="PyPI"))
    
    return packages


def query_osv(packages: List[PackageVersion], timeout: float = 10.0) -> Dict[str, List[dict]]:
    """Query OSV API for vulnerabilities in packages."""
    if not packages:
        return {}
    
    results = {}
    
    # Use batch API for efficiency
    queries = [pkg.to_osv_query() for pkg in packages]
    
    try:
        request_data = json.dumps({"queries": queries}).encode('utf-8')
        req = urllib.request.Request(
            OSV_BATCH_URL,
            data=request_data,
            headers={'Content-Type': 'application/json'},
            method='POST'
        )
        
        with urllib.request.urlopen(req, timeout=timeout) as response:
            data = json.loads(response.read())
    except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, json.JSONDecodeError) as e:
        # Network error - return empty but don't fail the scan
        return {}
    
    # Parse batch response
    for i, result in enumerate(data.get('results', [])):
        if i < len(packages):
            vulns = result.get('vulns', [])
            if vulns:
                pkg = packages[i]
                key = f"{pkg.ecosystem}:{pkg.name}"
                results[key] = vulns
    
    return results


def check_dependencies_for_cves(content: str, filename: str, timeout: float = 10.0) -> List[Finding]:
    """Check a dependency file for known CVEs."""
    findings = []
    
    # Parse based on file type
    fname = Path(filename).name.lower()
    
    if fname == 'requirements.txt' or fname.startswith('requirements'):
        packages = parse_requirements_txt(content)
    elif fname == 'package.json':
        packages = parse_package_json(content)
    elif fname == 'pyproject.toml':
        packages = parse_pyproject_toml(content)
    else:
        return findings
    
    if not packages:
        return findings
    
    # Query OSV for vulnerabilities
    vulns_by_pkg = query_osv(packages, timeout=timeout)
    
    # Generate findings for vulnerable packages
    for pkg in packages:
        key = f"{pkg.ecosystem}:{pkg.name}"
        vulns = vulns_by_pkg.get(key, [])
        
        for vuln in vulns:
            vuln_id = vuln.get('id', 'UNKNOWN')
            summary = vuln.get('summary', 'No summary available')
            severity_data = vuln.get('severity', [])
            
            # Determine severity from CVSS if available
            severity = Severity.WARN
            cvss_score = None
            for s in severity_data:
                if 'score' in s:
                    try:
                        cvss_score = float(s['score'])
                        if cvss_score >= 9.0:
                            severity = Severity.DANGER
                        elif cvss_score >= 7.0:
                            severity = Severity.DANGER
                        elif cvss_score >= 4.0:
                            severity = Severity.WARN
                    except (ValueError, TypeError):
                        pass
            
            # High severity CVEs are DANGER
            if vuln_id.startswith('CVE-') or vuln_id.startswith('GHSA-'):
                if any(kw in summary.lower() for kw in ('remote code', 'rce', 'arbitrary code', 'command injection', 'sql injection')):
                    severity = Severity.DANGER
            
            findings.append(Finding(
                severity=severity,
                pattern_id='known_vulnerability',
                description=f'{vuln_id}: {summary[:150]}',
                file=filename,
                line=1,  # We don't track line numbers in deps
                match=f'{pkg.name}=={pkg.version}' if pkg.version else pkg.name,
                context=f'CVSS: {cvss_score}' if cvss_score else '',
            ))
    
    return findings


def check_cves_enabled() -> bool:
    """Check if CVE checking is available (has network access)."""
    try:
        req = urllib.request.Request(
            "https://api.osv.dev/v1/",
            method='HEAD'
        )
        with urllib.request.urlopen(req, timeout=2.0):
            return True
    except Exception:
        return False
