"""SARIF reporter for security scan results.

SARIF (Static Analysis Results Interchange Format) is an OASIS standard
for expressing the output of static analysis tools.

Specification: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
"""

import json
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from aisentry.models.finding import Finding, Severity
from aisentry.models.result import ScanResult, TestResult
from aisentry.models.vulnerability import LiveVulnerability

from .base_reporter import BaseReporter

# SARIF severity level mapping
SEVERITY_TO_SARIF_LEVEL = {
    Severity.CRITICAL: "error",
    Severity.HIGH: "error",
    Severity.MEDIUM: "warning",
    Severity.LOW: "note",
    Severity.INFO: "note",
}

# SARIF security severity mapping (for GitHub Code Scanning)
SEVERITY_TO_SECURITY_SEVERITY = {
    Severity.CRITICAL: "critical",
    Severity.HIGH: "high",
    Severity.MEDIUM: "medium",
    Severity.LOW: "low",
    Severity.INFO: "low",
}

# OWASP LLM Top 10 Rules
OWASP_LLM_RULES = {
    "LLM01": {
        "id": "LLM01",
        "name": "PromptInjection",
        "shortDescription": {"text": "Prompt Injection"},
        "fullDescription": {
            "text": "Prompt Injection vulnerability occurs when an attacker manipulates a large language model (LLM) through crafted inputs, causing the LLM to unknowingly execute the attacker's intentions."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Implement input validation, use parameterized prompts, and apply output filtering to prevent prompt injection attacks.",
            "markdown": "## Prompt Injection\n\n**Risk:** Attackers can manipulate LLM behavior through malicious inputs.\n\n**Mitigation:**\n- Validate and sanitize all user inputs\n- Use parameterized prompts\n- Implement output filtering\n- Use context isolation between system and user prompts"
        },
        "defaultConfiguration": {"level": "error"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "injection"],
            "security-severity": "8.0"
        }
    },
    "LLM02": {
        "id": "LLM02",
        "name": "InsecureOutputHandling",
        "shortDescription": {"text": "Insecure Output Handling"},
        "fullDescription": {
            "text": "Insecure Output Handling refers to insufficient validation, sanitization, and handling of the outputs generated by large language models before they are passed to other components."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Treat LLM output as untrusted, apply output encoding, validate against schemas, and use content security policies.",
            "markdown": "## Insecure Output Handling\n\n**Risk:** LLM outputs may contain malicious content like XSS payloads or SQL injection.\n\n**Mitigation:**\n- Treat all LLM output as untrusted\n- Apply context-appropriate output encoding\n- Validate outputs against expected schemas\n- Implement Content Security Policy (CSP)"
        },
        "defaultConfiguration": {"level": "error"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "xss", "injection"],
            "security-severity": "7.5"
        }
    },
    "LLM03": {
        "id": "LLM03",
        "name": "TrainingDataPoisoning",
        "shortDescription": {"text": "Training Data Poisoning"},
        "fullDescription": {
            "text": "Training Data Poisoning occurs when training data is manipulated to introduce vulnerabilities, backdoors, or biases that compromise model security and effectiveness."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Verify training data sources, implement data validation pipelines, and monitor for anomalous model behavior.",
            "markdown": "## Training Data Poisoning\n\n**Risk:** Compromised training data can introduce backdoors or biases.\n\n**Mitigation:**\n- Verify and validate training data sources\n- Implement data sanitization pipelines\n- Use anomaly detection during training\n- Maintain data provenance records"
        },
        "defaultConfiguration": {"level": "warning"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "data-integrity"],
            "security-severity": "6.5"
        }
    },
    "LLM04": {
        "id": "LLM04",
        "name": "ModelDenialOfService",
        "shortDescription": {"text": "Model Denial of Service"},
        "fullDescription": {
            "text": "Model Denial of Service occurs when attackers interact with LLMs in ways that consume excessive resources, leading to service degradation or high costs."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Implement rate limiting, set token limits, use timeouts, and monitor resource consumption.",
            "markdown": "## Model Denial of Service\n\n**Risk:** Resource exhaustion through expensive LLM operations.\n\n**Mitigation:**\n- Implement rate limiting per user/API key\n- Set maximum token/context limits\n- Use request timeouts\n- Monitor and alert on resource consumption"
        },
        "defaultConfiguration": {"level": "warning"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "availability", "dos"],
            "security-severity": "5.5"
        }
    },
    "LLM05": {
        "id": "LLM05",
        "name": "SupplyChainVulnerabilities",
        "shortDescription": {"text": "Supply Chain Vulnerabilities"},
        "fullDescription": {
            "text": "Supply Chain Vulnerabilities in LLM applications can arise from compromised pre-trained models, poisoned training data from third parties, or vulnerable third-party plugins."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Verify model sources, use signed models, scan dependencies, and maintain software bill of materials.",
            "markdown": "## Supply Chain Vulnerabilities\n\n**Risk:** Compromised models, datasets, or dependencies.\n\n**Mitigation:**\n- Use models from trusted sources only\n- Verify model signatures and checksums\n- Scan third-party dependencies\n- Maintain Software Bill of Materials (SBOM)"
        },
        "defaultConfiguration": {"level": "error"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "supply-chain"],
            "security-severity": "7.0"
        }
    },
    "LLM06": {
        "id": "LLM06",
        "name": "SensitiveInformationDisclosure",
        "shortDescription": {"text": "Sensitive Information Disclosure"},
        "fullDescription": {
            "text": "Sensitive Information Disclosure occurs when LLMs inadvertently reveal confidential data through their responses, potentially exposing PII, credentials, or proprietary information."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Implement data sanitization, use output filters, apply access controls, and avoid training on sensitive data.",
            "markdown": "## Sensitive Information Disclosure\n\n**Risk:** Exposure of PII, credentials, or proprietary data.\n\n**Mitigation:**\n- Sanitize training data to remove sensitive information\n- Implement output filtering for PII/secrets\n- Apply principle of least privilege\n- Use data loss prevention (DLP) tools"
        },
        "defaultConfiguration": {"level": "error"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "data-exposure", "pii"],
            "security-severity": "8.5"
        }
    },
    "LLM07": {
        "id": "LLM07",
        "name": "InsecurePluginDesign",
        "shortDescription": {"text": "Insecure Plugin Design"},
        "fullDescription": {
            "text": "Insecure Plugin Design occurs when LLM plugins lack proper input validation, have excessive permissions, or fail to implement adequate security controls."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Validate all plugin inputs, apply least privilege, use sandboxing, and require user confirmation for sensitive actions.",
            "markdown": "## Insecure Plugin Design\n\n**Risk:** Plugins may execute malicious actions or expose sensitive functionality.\n\n**Mitigation:**\n- Validate and sanitize all plugin inputs\n- Apply principle of least privilege\n- Use sandboxing for plugin execution\n- Require user confirmation for sensitive actions"
        },
        "defaultConfiguration": {"level": "error"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "plugins"],
            "security-severity": "7.5"
        }
    },
    "LLM08": {
        "id": "LLM08",
        "name": "ExcessiveAgency",
        "shortDescription": {"text": "Excessive Agency"},
        "fullDescription": {
            "text": "Excessive Agency occurs when LLM-based systems are granted too much autonomy to take actions, potentially leading to unintended consequences from hallucinations or malicious prompts."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Limit LLM permissions, require human approval for critical actions, implement logging, and use rate limiting.",
            "markdown": "## Excessive Agency\n\n**Risk:** LLMs may take unintended actions with real-world consequences.\n\n**Mitigation:**\n- Limit LLM permissions to minimum necessary\n- Require human-in-the-loop for critical actions\n- Implement comprehensive logging\n- Use rate limiting and action budgets"
        },
        "defaultConfiguration": {"level": "warning"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "authorization"],
            "security-severity": "6.0"
        }
    },
    "LLM09": {
        "id": "LLM09",
        "name": "Overreliance",
        "shortDescription": {"text": "Overreliance"},
        "fullDescription": {
            "text": "Overreliance on LLMs occurs when systems or users trust LLM outputs without adequate verification, potentially leading to misinformation, security vulnerabilities, or faulty code."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Implement human review processes, validate LLM outputs, provide confidence scores, and educate users about limitations.",
            "markdown": "## Overreliance\n\n**Risk:** Trusting LLM outputs without verification can lead to errors.\n\n**Mitigation:**\n- Implement human review for critical decisions\n- Validate LLM outputs against trusted sources\n- Display confidence scores and limitations\n- Educate users about LLM limitations"
        },
        "defaultConfiguration": {"level": "note"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "trust"],
            "security-severity": "4.0"
        }
    },
    "LLM10": {
        "id": "LLM10",
        "name": "ModelTheft",
        "shortDescription": {"text": "Model Theft"},
        "fullDescription": {
            "text": "Model Theft refers to unauthorized access, copying, or extraction of proprietary LLM models, leading to economic loss, competitive disadvantage, and potential security risks."
        },
        "helpUri": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
        "help": {
            "text": "Implement access controls, use API rate limiting, monitor for extraction attempts, and apply watermarking.",
            "markdown": "## Model Theft\n\n**Risk:** Unauthorized access or extraction of proprietary models.\n\n**Mitigation:**\n- Implement strong access controls\n- Use API rate limiting\n- Monitor for model extraction attempts\n- Apply model watermarking techniques"
        },
        "defaultConfiguration": {"level": "error"},
        "properties": {
            "tags": ["security", "owasp-llm-top-10", "intellectual-property"],
            "security-severity": "7.0"
        }
    },
}


class SARIFReporter(BaseReporter):
    """
    Generates SARIF (Static Analysis Results Interchange Format) reports.

    SARIF is supported by:
    - GitHub Code Scanning
    - Azure DevOps
    - VS Code SARIF Viewer
    - Many CI/CD tools
    """

    SARIF_VERSION = "2.1.0"
    SARIF_SCHEMA = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
    TOOL_NAME = "aisentry"
    TOOL_VERSION = "1.0.0"
    TOOL_INFO_URI = "https://github.com/aisentry/aisentry"

    def __init__(self, pretty: bool = True, verbose: bool = False):
        """
        Initialize SARIF reporter.

        Args:
            pretty: Use indented formatting
            verbose: Include additional debug information
        """
        super().__init__(verbose=verbose)
        self.pretty = pretty
        self.indent = 2 if pretty else None

    def generate_scan_report(self, result: ScanResult) -> str:
        """Generate SARIF report for static scan results."""
        sarif = {
            "$schema": self.SARIF_SCHEMA,
            "version": self.SARIF_VERSION,
            "runs": [
                {
                    "tool": self._build_tool_component(),
                    "results": self._build_results(result.findings),
                    "invocations": [
                        {
                            "executionSuccessful": True,
                            "endTimeUtc": datetime.now(timezone.utc).isoformat(),
                            "workingDirectory": {
                                "uri": f"file://{result.target_path}"
                            }
                        }
                    ],
                    "automationDetails": {
                        "id": f"aisentry/static-scan/{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
                    }
                }
            ]
        }

        return json.dumps(sarif, indent=self.indent, default=str)

    def generate_test_report(self, result: TestResult) -> str:
        """Generate SARIF report for live test results."""
        sarif = {
            "$schema": self.SARIF_SCHEMA,
            "version": self.SARIF_VERSION,
            "runs": [
                {
                    "tool": self._build_tool_component(include_live_rules=True),
                    "results": self._build_vuln_results(result.vulnerabilities),
                    "invocations": [
                        {
                            "executionSuccessful": True,
                            "endTimeUtc": datetime.now(timezone.utc).isoformat(),
                            "properties": {
                                "provider": result.provider,
                                "model": result.model,
                                "mode": result.mode,
                                "testsRun": result.tests_run,
                                "testsPassed": result.tests_passed
                            }
                        }
                    ],
                    "automationDetails": {
                        "id": f"aisentry/live-test/{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
                    }
                }
            ]
        }

        return json.dumps(sarif, indent=self.indent, default=str)

    def generate_unified_report(self, result) -> str:
        """Generate SARIF report for unified results (static + live)."""
        runs = []

        # Add static scan results if present (use correct field name: static_result)
        if result.static_result:
            runs.append({
                "tool": self._build_tool_component(),
                "results": self._build_results(result.static_result.findings),
                "invocations": [
                    {
                        "executionSuccessful": True,
                        "endTimeUtc": datetime.now(timezone.utc).isoformat(),
                    }
                ],
                "automationDetails": {
                    "id": f"aisentry/unified-static/{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
                }
            })

        # Add live test results if present (use correct field name: live_result)
        if result.live_result:
            runs.append({
                "tool": self._build_tool_component(include_live_rules=True),
                "results": self._build_vuln_results(result.live_result.vulnerabilities),
                "invocations": [
                    {
                        "executionSuccessful": True,
                        "endTimeUtc": datetime.now(timezone.utc).isoformat(),
                    }
                ],
                "automationDetails": {
                    "id": f"aisentry/unified-live/{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"
                }
            })

        sarif = {
            "$schema": self.SARIF_SCHEMA,
            "version": self.SARIF_VERSION,
            "runs": runs
        }

        return json.dumps(sarif, indent=self.indent, default=str)

    def _build_tool_component(self, include_live_rules: bool = False) -> Dict[str, Any]:
        """Build the tool component with rules."""
        rules = list(OWASP_LLM_RULES.values())

        if include_live_rules:
            # Add live testing specific rules
            live_rules = [
                {
                    "id": "LIVE-PROMPT-INJECTION",
                    "name": "LivePromptInjection",
                    "shortDescription": {"text": "Live Prompt Injection Test"},
                    "fullDescription": {"text": "Model responded to prompt injection attempt during live testing."},
                    "defaultConfiguration": {"level": "error"},
                    "properties": {"tags": ["security", "live-test"], "security-severity": "8.0"}
                },
                {
                    "id": "LIVE-JAILBREAK",
                    "name": "LiveJailbreak",
                    "shortDescription": {"text": "Live Jailbreak Test"},
                    "fullDescription": {"text": "Model was successfully jailbroken during live testing."},
                    "defaultConfiguration": {"level": "error"},
                    "properties": {"tags": ["security", "live-test"], "security-severity": "9.0"}
                },
                {
                    "id": "LIVE-DATA-LEAKAGE",
                    "name": "LiveDataLeakage",
                    "shortDescription": {"text": "Live Data Leakage Test"},
                    "fullDescription": {"text": "Model leaked sensitive information during live testing."},
                    "defaultConfiguration": {"level": "error"},
                    "properties": {"tags": ["security", "live-test"], "security-severity": "8.5"}
                },
            ]
            rules.extend(live_rules)

        return {
            "driver": {
                "name": self.TOOL_NAME,
                "version": self.TOOL_VERSION,
                "informationUri": self.TOOL_INFO_URI,
                "rules": rules,
                "properties": {
                    "tags": ["security", "llm", "ai", "owasp"]
                }
            }
        }

    def _build_results(self, findings: List[Finding]) -> List[Dict[str, Any]]:
        """Build SARIF results from findings."""
        results = []

        for idx, finding in enumerate(findings):
            # Map category to rule ID
            rule_id = self._extract_rule_id(finding.category)

            result = {
                "ruleId": rule_id,
                "ruleIndex": self._get_rule_index(rule_id),
                "level": SEVERITY_TO_SARIF_LEVEL.get(finding.severity, "warning"),
                "message": {
                    "text": finding.description or finding.title,
                    "markdown": self._build_markdown_message(finding)
                },
                "locations": [
                    {
                        "physicalLocation": {
                            "artifactLocation": {
                                "uri": self._normalize_path(finding.file_path),
                                "uriBaseId": "%SRCROOT%"
                            },
                            "region": {
                                "startLine": finding.line_number or 1,
                                "startColumn": 1
                            }
                        }
                    }
                ],
                "fingerprints": {
                    "primaryLocationLineHash": f"{finding.id}-{finding.line_number}"
                },
                "properties": {
                    "security-severity": self._get_security_severity(finding.severity),
                    "confidence": finding.confidence,
                    "category": finding.category
                }
            }

            # Add code snippet if available
            if finding.code_snippet:
                result["locations"][0]["physicalLocation"]["region"]["snippet"] = {
                    "text": finding.code_snippet
                }

            # Add remediation as related location or fix suggestion
            if finding.recommendation:
                result["fixes"] = [
                    {
                        "description": {
                            "text": finding.recommendation
                        }
                    }
                ]

            results.append(result)

        return results

    def _build_vuln_results(self, vulns: List[LiveVulnerability]) -> List[Dict[str, Any]]:
        """Build SARIF results from live vulnerabilities."""
        results = []

        for vuln in vulns:
            # Map detector to rule ID
            rule_id = f"LIVE-{vuln.detector_id.upper().replace('-', '-')}" if vuln.detector_id else "LIVE-UNKNOWN"

            result = {
                "ruleId": rule_id,
                "level": SEVERITY_TO_SARIF_LEVEL.get(vuln.severity, "warning"),
                "message": {
                    "text": vuln.description or vuln.title,
                    "markdown": f"**{vuln.title}**\n\n{vuln.description}\n\n**Prompt Used:**\n```\n{vuln.prompt_used[:500]}\n```"
                },
                "properties": {
                    "security-severity": self._get_security_severity(vuln.severity),
                    "confidence": vuln.confidence,
                    "detector": vuln.detector_id,
                    "evidence": vuln.evidence
                }
            }

            if vuln.remediation:
                result["fixes"] = [
                    {
                        "description": {
                            "text": vuln.remediation
                        }
                    }
                ]

            results.append(result)

        return results

    def _extract_rule_id(self, category: Optional[str]) -> str:
        """Extract OWASP LLM rule ID from category string."""
        if not category:
            return "LLM01"

        # Handle formats like "LLM01: Prompt Injection" or just "LLM01"
        category_upper = category.upper()
        for rule_id in OWASP_LLM_RULES.keys():
            if rule_id in category_upper:
                return rule_id

        return "LLM01"  # Default

    def _get_rule_index(self, rule_id: str) -> int:
        """Get the index of a rule in the rules array."""
        rule_ids = list(OWASP_LLM_RULES.keys())
        try:
            return rule_ids.index(rule_id)
        except ValueError:
            return 0

    def _normalize_path(self, path: Optional[str]) -> str:
        """Normalize file path for SARIF URI format."""
        if not path:
            return "unknown"

        # Remove temp directory prefixes for cleaner paths
        if "/aisentry-scan-" in path:
            parts = path.split("/aisentry-scan-")
            if len(parts) > 1:
                # Get everything after the temp dir name
                remaining = parts[1]
                if "/" in remaining:
                    return remaining.split("/", 1)[1]

        return path

    def _get_security_severity(self, severity: Severity) -> str:
        """Get numeric security severity for GitHub Code Scanning."""
        severity_scores = {
            Severity.CRITICAL: "9.0",
            Severity.HIGH: "7.5",
            Severity.MEDIUM: "5.0",
            Severity.LOW: "3.0",
            Severity.INFO: "1.0",
        }
        return severity_scores.get(severity, "5.0")

    def _build_markdown_message(self, finding: Finding) -> str:
        """Build a rich markdown message for the finding."""
        parts = [f"**{finding.title}**"]

        if finding.description:
            parts.append(f"\n\n{finding.description}")

        if finding.code_snippet:
            parts.append(f"\n\n**Code:**\n```python\n{finding.code_snippet}\n```")

        if finding.recommendation:
            parts.append(f"\n\n**Remediation:**\n{finding.recommendation}")

        return "".join(parts)
