"""
Security Report Parsers & Utility Functions

Parsing logic for security tool reports (Gitleaks, Semgrep, Bandit, Trivy, Grype).
Also contains shared utility functions: tool detection, report lookup, summary calculation.
"""

import json
import subprocess
from datetime import datetime
from pathlib import Path
from typing import List, Optional

from security_models import Finding, SeveritySummary


# Pfade - innerhalb des Backend-Verzeichnisses
# In Docker: /app/security-reports, /app/scripts
# Lokal: backend/security-reports, backend/scripts
BACKEND_DIR = Path(__file__).parent
REPORTS_DIR = BACKEND_DIR / "security-reports"
SCRIPTS_DIR = BACKEND_DIR / "scripts"

# Projekt-Root fuer Security-Scans
PROJECT_ROOT = BACKEND_DIR

# Sicherstellen, dass das Reports-Verzeichnis existiert
try:
    REPORTS_DIR.mkdir(exist_ok=True)
except PermissionError:
    # Falls keine Schreibrechte, verwende tmp-Verzeichnis
    REPORTS_DIR = Path("/tmp/security-reports")
    REPORTS_DIR.mkdir(exist_ok=True)


# ===========================
# Utility Functions
# ===========================

def check_tool_installed(tool_name: str) -> tuple[bool, Optional[str]]:
    """Prueft, ob ein Tool installiert ist und gibt die Version zurueck."""
    try:
        if tool_name == "gitleaks":
            result = subprocess.run(["gitleaks", "version"], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return True, result.stdout.strip()
        elif tool_name == "semgrep":
            result = subprocess.run(["semgrep", "--version"], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return True, result.stdout.strip().split('\n')[0]
        elif tool_name == "bandit":
            result = subprocess.run(["bandit", "--version"], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return True, result.stdout.strip()
        elif tool_name == "trivy":
            result = subprocess.run(["trivy", "version"], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                # Parse "Version: 0.48.x"
                for line in result.stdout.split('\n'):
                    if line.startswith('Version:'):
                        return True, line.split(':')[1].strip()
                return True, result.stdout.strip().split('\n')[0]
        elif tool_name == "grype":
            result = subprocess.run(["grype", "version"], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return True, result.stdout.strip().split('\n')[0]
        elif tool_name == "syft":
            result = subprocess.run(["syft", "version"], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return True, result.stdout.strip().split('\n')[0]
    except (subprocess.TimeoutExpired, FileNotFoundError):
        pass
    return False, None


def get_latest_report(tool_prefix: str) -> Optional[Path]:
    """Findet den neuesten Report fuer ein Tool."""
    if not REPORTS_DIR.exists():
        return None

    reports = list(REPORTS_DIR.glob(f"{tool_prefix}*.json"))
    if not reports:
        return None

    return max(reports, key=lambda p: p.stat().st_mtime)


# ===========================
# Report Parsers
# ===========================

def parse_gitleaks_report(report_path: Path) -> List[Finding]:
    """Parst Gitleaks JSON Report."""
    findings = []
    try:
        with open(report_path) as f:
            data = json.load(f)
            if isinstance(data, list):
                for item in data:
                    findings.append(Finding(
                        id=item.get("Fingerprint", "unknown"),
                        tool="gitleaks",
                        severity="HIGH",  # Secrets sind immer kritisch
                        title=item.get("Description", "Secret detected"),
                        message=f"Rule: {item.get('RuleID', 'unknown')}",
                        file=item.get("File", ""),
                        line=item.get("StartLine", 0),
                        found_at=datetime.fromtimestamp(report_path.stat().st_mtime).isoformat()
                    ))
    except (json.JSONDecodeError, KeyError, FileNotFoundError):
        pass
    return findings


def parse_semgrep_report(report_path: Path) -> List[Finding]:
    """Parst Semgrep JSON Report."""
    findings = []
    try:
        with open(report_path) as f:
            data = json.load(f)
            results = data.get("results", [])
            for item in results:
                severity = item.get("extra", {}).get("severity", "INFO").upper()
                findings.append(Finding(
                    id=item.get("check_id", "unknown"),
                    tool="semgrep",
                    severity=severity,
                    title=item.get("extra", {}).get("message", "Finding"),
                    message=item.get("check_id", ""),
                    file=item.get("path", ""),
                    line=item.get("start", {}).get("line", 0),
                    found_at=datetime.fromtimestamp(report_path.stat().st_mtime).isoformat()
                ))
    except (json.JSONDecodeError, KeyError, FileNotFoundError):
        pass
    return findings


def parse_bandit_report(report_path: Path) -> List[Finding]:
    """Parst Bandit JSON Report."""
    findings = []
    try:
        with open(report_path) as f:
            data = json.load(f)
            results = data.get("results", [])
            for item in results:
                severity = item.get("issue_severity", "LOW").upper()
                findings.append(Finding(
                    id=item.get("test_id", "unknown"),
                    tool="bandit",
                    severity=severity,
                    title=item.get("issue_text", "Finding"),
                    message=f"CWE: {item.get('issue_cwe', {}).get('id', 'N/A')}",
                    file=item.get("filename", ""),
                    line=item.get("line_number", 0),
                    found_at=datetime.fromtimestamp(report_path.stat().st_mtime).isoformat()
                ))
    except (json.JSONDecodeError, KeyError, FileNotFoundError):
        pass
    return findings


def parse_trivy_report(report_path: Path) -> List[Finding]:
    """Parst Trivy JSON Report."""
    findings = []
    try:
        with open(report_path) as f:
            data = json.load(f)
            results = data.get("Results", [])
            for result in results:
                vulnerabilities = result.get("Vulnerabilities", []) or []
                target = result.get("Target", "")
                for vuln in vulnerabilities:
                    severity = vuln.get("Severity", "UNKNOWN").upper()
                    findings.append(Finding(
                        id=vuln.get("VulnerabilityID", "unknown"),
                        tool="trivy",
                        severity=severity,
                        title=vuln.get("Title", vuln.get("VulnerabilityID", "CVE")),
                        message=f"{vuln.get('PkgName', '')} {vuln.get('InstalledVersion', '')}",
                        file=target,
                        line=None,
                        found_at=datetime.fromtimestamp(report_path.stat().st_mtime).isoformat()
                    ))
    except (json.JSONDecodeError, KeyError, FileNotFoundError):
        pass
    return findings


def parse_grype_report(report_path: Path) -> List[Finding]:
    """Parst Grype JSON Report."""
    findings = []
    try:
        with open(report_path) as f:
            data = json.load(f)
            matches = data.get("matches", [])
            for match in matches:
                vuln = match.get("vulnerability", {})
                artifact = match.get("artifact", {})
                severity = vuln.get("severity", "Unknown").upper()
                findings.append(Finding(
                    id=vuln.get("id", "unknown"),
                    tool="grype",
                    severity=severity,
                    title=vuln.get("description", vuln.get("id", "CVE"))[:100],
                    message=f"{artifact.get('name', '')} {artifact.get('version', '')}",
                    file=artifact.get("locations", [{}])[0].get("path", "") if artifact.get("locations") else "",
                    line=None,
                    found_at=datetime.fromtimestamp(report_path.stat().st_mtime).isoformat()
                ))
    except (json.JSONDecodeError, KeyError, FileNotFoundError):
        pass
    return findings


# ===========================
# Aggregation Functions
# ===========================

def get_all_findings() -> List[Finding]:
    """Sammelt alle Findings aus allen Reports."""
    findings = []

    # Gitleaks
    gitleaks_report = get_latest_report("gitleaks")
    if gitleaks_report:
        findings.extend(parse_gitleaks_report(gitleaks_report))

    # Semgrep
    semgrep_report = get_latest_report("semgrep")
    if semgrep_report:
        findings.extend(parse_semgrep_report(semgrep_report))

    # Bandit
    bandit_report = get_latest_report("bandit")
    if bandit_report:
        findings.extend(parse_bandit_report(bandit_report))

    # Trivy (filesystem)
    trivy_fs_report = get_latest_report("trivy-fs")
    if trivy_fs_report:
        findings.extend(parse_trivy_report(trivy_fs_report))

    # Grype
    grype_report = get_latest_report("grype")
    if grype_report:
        findings.extend(parse_grype_report(grype_report))

    return findings


def calculate_summary(findings: List[Finding]) -> SeveritySummary:
    """Berechnet die Severity-Zusammenfassung."""
    summary = SeveritySummary()
    for finding in findings:
        severity = finding.severity.upper()
        if severity == "CRITICAL":
            summary.critical += 1
        elif severity == "HIGH":
            summary.high += 1
        elif severity == "MEDIUM":
            summary.medium += 1
        elif severity == "LOW":
            summary.low += 1
        else:
            summary.info += 1
    summary.total = len(findings)
    return summary
