#!/usr/bin/env python3
"""
Donjon Platform - Demo Mode
Generates realistic simulated scan data for "Acme Corp" so prospects can
see the platform's value in 60 seconds without needing real targets.

Usage:
    donjon --demo
    donjon demo
"""

import json
import os
import sys
import threading
import time
import webbrowser
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Dict, List, Optional

# Ensure lib/ is importable
sys.path.insert(0, str(Path(__file__).resolve().parent))

from evidence import get_evidence_manager
from config import Config
from interactive_report import generate as generate_interactive_report
from paths import paths


# ---------------------------------------------------------------------------
# Demo Data — realistic findings with real CVEs and NIST controls
# ---------------------------------------------------------------------------

DEMO_SESSION_META = {
    "scan_type": "deep",
    "target_org": "Acme Corp",
    "target_networks": ["10.0.1.0/24", "10.0.2.0/24", "acme-corp.example.com"],
}

# 10 hosts discovered
DEMO_HOSTS = [
    "10.0.1.1",   # gateway/firewall
    "10.0.1.10",  # domain controller
    "10.0.1.11",  # file server
    "10.0.1.20",  # web server (public)
    "10.0.1.21",  # web app server
    "10.0.1.30",  # database server
    "10.0.1.40",  # mail server
    "10.0.1.50",  # dev workstation
    "10.0.2.10",  # cloud jump host
    "10.0.2.20",  # CI/CD runner
]

# 25 open ports across hosts
DEMO_OPEN_PORTS = [
    ("10.0.1.1", 22), ("10.0.1.1", 443), ("10.0.1.1", 8443),
    ("10.0.1.10", 53), ("10.0.1.10", 88), ("10.0.1.10", 389), ("10.0.1.10", 636),
    ("10.0.1.11", 139), ("10.0.1.11", 445),
    ("10.0.1.20", 80), ("10.0.1.20", 443), ("10.0.1.20", 8080),
    ("10.0.1.21", 443), ("10.0.1.21", 8443),
    ("10.0.1.30", 3306), ("10.0.1.30", 5432),
    ("10.0.1.40", 25), ("10.0.1.40", 587), ("10.0.1.40", 993),
    ("10.0.1.50", 22), ("10.0.1.50", 3389),
    ("10.0.2.10", 22), ("10.0.2.10", 443),
    ("10.0.2.20", 22), ("10.0.2.20", 8080),
]


def _network_findings() -> List[Dict]:
    """8 network scan findings: 2 critical, 3 high, 2 medium, 1 low."""
    return [
        {
            "severity": "CRITICAL",
            "title": "OpenSSH Pre-Auth Remote Code Execution (regreSSHion)",
            "description": (
                "OpenSSH server on 10.0.2.10:22 is running version 8.5p1, which is "
                "vulnerable to CVE-2024-6387 (regreSSHion). A remote unauthenticated "
                "attacker can exploit a signal handler race condition to achieve RCE "
                "as root. This vulnerability has been actively exploited in the wild."
            ),
            "affected_asset": "10.0.2.10:22",
            "cvss_score": 8.1,
            "cve_ids": ["CVE-2024-6387"],
            "remediation": (
                "Upgrade OpenSSH to 9.8p1 or later immediately. If patching is not "
                "possible within 24 hours, set LoginGraceTime to 0 in sshd_config "
                "as a temporary mitigation (note: this enables DoS risk)."
            ),
            "scanner_name": "nmap",
        },
        {
            "severity": "CRITICAL",
            "title": "Microsoft SQL Server Remote Code Execution",
            "description": (
                "Microsoft SQL Server on 10.0.1.30:3306 is vulnerable to "
                "CVE-2023-21554 (QueueJumper). A remote unauthenticated attacker "
                "can execute arbitrary code via a specially crafted MSMQ packet. "
                "CVSS 9.8 — no authentication required."
            ),
            "affected_asset": "10.0.1.30:3306",
            "cvss_score": 9.8,
            "cve_ids": ["CVE-2023-21554"],
            "remediation": (
                "Apply Microsoft Security Update KB5025228 immediately. Restrict "
                "network access to the database server to authorized application "
                "servers only via firewall rules."
            ),
            "scanner_name": "nmap",
        },
        {
            "severity": "HIGH",
            "title": "SMBv1 Enabled on File Server",
            "description": (
                "SMBv1 is enabled on 10.0.1.11:445. SMBv1 is a deprecated protocol "
                "with known vulnerabilities including EternalBlue (CVE-2017-0144). "
                "Even if patched, SMBv1 lacks modern security features like encryption "
                "and pre-authentication integrity."
            ),
            "affected_asset": "10.0.1.11:445",
            "cvss_score": 8.1,
            "cve_ids": ["CVE-2017-0144"],
            "remediation": (
                "Disable SMBv1 on all systems: "
                "Set-SmbServerConfiguration -EnableSMB1Protocol $false. "
                "Verify all clients support SMBv2/v3 before disabling."
            ),
            "scanner_name": "nmap",
        },
        {
            "severity": "HIGH",
            "title": "LDAP Signing Not Required on Domain Controller",
            "description": (
                "Active Directory LDAP on 10.0.1.10:389 does not require signing. "
                "An attacker on the network can perform LDAP relay attacks to "
                "escalate privileges or modify directory objects. This is a common "
                "attack vector in Active Directory environments."
            ),
            "affected_asset": "10.0.1.10:389",
            "cvss_score": 7.5,
            "cve_ids": ["CVE-2017-8563"],
            "remediation": (
                "Enable LDAP signing on all domain controllers via Group Policy: "
                "Computer Configuration > Policies > Windows Settings > Security "
                "Settings > Local Policies > Security Options > "
                "'Domain controller: LDAP server signing requirements' = 'Require signing'."
            ),
            "scanner_name": "nmap",
        },
        {
            "severity": "HIGH",
            "title": "TLS 1.0/1.1 Enabled on Mail Server",
            "description": (
                "Mail server at 10.0.1.40:993 supports TLS 1.0 and TLS 1.1, which "
                "are deprecated protocols with known cryptographic weaknesses "
                "(BEAST, POODLE). PCI DSS 4.0 requires TLS 1.2 minimum."
            ),
            "affected_asset": "10.0.1.40:993",
            "cvss_score": 7.4,
            "cve_ids": ["CVE-2011-3389", "CVE-2014-3566"],
            "remediation": (
                "Disable TLS 1.0 and 1.1 on the mail server. Configure minimum "
                "TLS version to 1.2. Update cipher suite to prefer AEAD ciphers "
                "(AES-256-GCM, CHACHA20-POLY1305)."
            ),
            "scanner_name": "testssl",
        },
        {
            "severity": "MEDIUM",
            "title": "PostgreSQL Accessible from Non-Application Hosts",
            "description": (
                "PostgreSQL on 10.0.1.30:5432 accepts connections from the entire "
                "10.0.1.0/24 subnet. Database access should be restricted to "
                "application servers only to limit blast radius of a compromise."
            ),
            "affected_asset": "10.0.1.30:5432",
            "cvss_score": 5.3,
            "cve_ids": [],
            "remediation": (
                "Update pg_hba.conf to restrict connections to specific application "
                "server IPs (10.0.1.20, 10.0.1.21). Add firewall rules to block "
                "port 5432 from all other hosts."
            ),
            "scanner_name": "nmap",
        },
        {
            "severity": "MEDIUM",
            "title": "RDP Exposed on Developer Workstation",
            "description": (
                "RDP (port 3389) is exposed on developer workstation 10.0.1.50. "
                "RDP is a frequent target for brute-force attacks and has had "
                "critical vulnerabilities (BlueKeep). Workstation RDP should not "
                "be accessible from the broader network."
            ),
            "affected_asset": "10.0.1.50:3389",
            "cvss_score": 6.1,
            "cve_ids": ["CVE-2019-0708"],
            "remediation": (
                "Disable RDP on workstations or restrict via Windows Firewall to "
                "IT admin subnet only. Use a VPN or jump host for remote access. "
                "Enable Network Level Authentication (NLA)."
            ),
            "scanner_name": "nmap",
        },
        {
            "severity": "LOW",
            "title": "SNMP v2c Community String 'public' on Gateway",
            "description": (
                "The gateway at 10.0.1.1 responds to SNMP v2c queries with the "
                "default community string 'public'. While read-only, this leaks "
                "network topology, interface details, and routing information."
            ),
            "affected_asset": "10.0.1.1:161",
            "cvss_score": 3.1,
            "cve_ids": [],
            "remediation": (
                "Change SNMP community strings from defaults. Preferably upgrade "
                "to SNMPv3 with authentication and encryption. Restrict SNMP "
                "access to management VLAN only."
            ),
            "scanner_name": "nmap",
        },
    ]


def _web_findings() -> List[Dict]:
    """5 web application findings."""
    return [
        {
            "severity": "CRITICAL",
            "title": "SQL Injection in Login Form",
            "description": (
                "The login form at https://acme-corp.example.com/api/auth/login is "
                "vulnerable to SQL injection via the 'username' parameter. A crafted "
                "payload such as ' OR 1=1-- bypasses authentication entirely. The "
                "application uses string concatenation for SQL queries instead of "
                "parameterized statements."
            ),
            "affected_asset": "acme-corp.example.com:443",
            "cvss_score": 9.8,
            "cve_ids": ["CWE-89"],
            "remediation": (
                "Replace all SQL string concatenation with parameterized queries / "
                "prepared statements. Implement input validation with allow-list "
                "patterns. Deploy a WAF rule to block SQLi patterns as defense-in-depth."
            ),
            "scanner_name": "nuclei",
        },
        {
            "severity": "HIGH",
            "title": "Stored Cross-Site Scripting (XSS) in User Profile",
            "description": (
                "The user profile 'bio' field at https://acme-corp.example.com/profile "
                "does not sanitize HTML input. An attacker can inject <script> tags "
                "that execute in the context of other users viewing the profile, "
                "enabling session hijacking and account takeover."
            ),
            "affected_asset": "acme-corp.example.com:443",
            "cvss_score": 7.1,
            "cve_ids": ["CWE-79"],
            "remediation": (
                "Implement output encoding (HTML entity escaping) on all user-supplied "
                "content. Use Content-Security-Policy header with script-src 'self'. "
                "Sanitize input with a library like DOMPurify on the client side."
            ),
            "scanner_name": "nuclei",
        },
        {
            "severity": "MEDIUM",
            "title": "Missing Security Headers",
            "description": (
                "The web server at acme-corp.example.com is missing critical security "
                "headers: Content-Security-Policy, X-Content-Type-Options, "
                "Strict-Transport-Security (HSTS), and Permissions-Policy. This "
                "increases the attack surface for XSS, clickjacking, and MIME "
                "confusion attacks."
            ),
            "affected_asset": "acme-corp.example.com:443",
            "cvss_score": 5.3,
            "cve_ids": ["CWE-693"],
            "remediation": (
                "Add the following response headers: "
                "Content-Security-Policy: default-src 'self'; "
                "X-Content-Type-Options: nosniff; "
                "Strict-Transport-Security: max-age=31536000; includeSubDomains; "
                "Permissions-Policy: camera=(), microphone=(), geolocation=()."
            ),
            "scanner_name": "nikto",
        },
        {
            "severity": "MEDIUM",
            "title": "Directory Listing Enabled on Static Assets",
            "description": (
                "Directory listing is enabled at https://acme-corp.example.com/static/ "
                "and /uploads/. This exposes internal file structure and may reveal "
                "sensitive files such as configuration backups, log files, or "
                "database dumps inadvertently placed in web-accessible directories."
            ),
            "affected_asset": "acme-corp.example.com:443",
            "cvss_score": 5.3,
            "cve_ids": ["CWE-548"],
            "remediation": (
                "Disable directory listing in the web server configuration. "
                "For Nginx: autoindex off; For Apache: Options -Indexes. "
                "Audit /uploads/ and /static/ for sensitive files."
            ),
            "scanner_name": "nikto",
        },
        {
            "severity": "LOW",
            "title": "Server Version Disclosed in HTTP Headers",
            "description": (
                "The web server at acme-corp.example.com discloses its version in "
                "the Server header: 'nginx/1.18.0 (Ubuntu)'. This information helps "
                "attackers identify known vulnerabilities for the specific version."
            ),
            "affected_asset": "acme-corp.example.com:443",
            "cvss_score": 3.1,
            "cve_ids": ["CWE-200"],
            "remediation": (
                "Suppress server version in HTTP headers. "
                "For Nginx: server_tokens off; "
                "For Apache: ServerTokens Prod."
            ),
            "scanner_name": "nikto",
        },
    ]


def _sbom_findings() -> List[Dict]:
    """3 SBOM/supply chain findings."""
    return [
        {
            "severity": "HIGH",
            "title": "Log4j 2.14.1 Detected in Java Application (Log4Shell)",
            "description": (
                "The CI/CD runner at 10.0.2.20 uses Apache Log4j 2.14.1, which is "
                "vulnerable to CVE-2021-44228 (Log4Shell). This allows remote code "
                "execution via crafted JNDI lookup strings in logged data. Despite "
                "being discovered in 2021, this remains one of the most exploited "
                "vulnerabilities worldwide."
            ),
            "affected_asset": "10.0.2.20:8080",
            "cvss_score": 10.0,
            "cve_ids": ["CVE-2021-44228"],
            "remediation": (
                "Upgrade Log4j to 2.21.0+ immediately. As temporary mitigation, "
                "set log4j2.formatMsgNoLookups=true or remove JndiLookup class from "
                "the classpath. Audit all Java applications for Log4j dependencies."
            ),
            "scanner_name": "sbom",
        },
        {
            "severity": "MEDIUM",
            "title": "Outdated OpenSSL 1.1.1 in Container Images",
            "description": (
                "Container images on 10.0.2.20 use OpenSSL 1.1.1, which reached "
                "end-of-life on 2023-09-11. No further security patches will be "
                "provided. Multiple CVEs affect 1.1.1 series including "
                "CVE-2023-5678 (certificate verification bypass)."
            ),
            "affected_asset": "10.0.2.20",
            "cvss_score": 6.5,
            "cve_ids": ["CVE-2023-5678", "CVE-2023-5363"],
            "remediation": (
                "Rebuild container images with base images that include OpenSSL 3.x "
                "(e.g., Ubuntu 22.04+, Alpine 3.18+). Add container image scanning "
                "to the CI/CD pipeline to catch EOL libraries before deployment."
            ),
            "scanner_name": "sbom",
        },
        {
            "severity": "HIGH",
            "title": "Known Vulnerable Python Package: PyYAML 5.3.1",
            "description": (
                "The web application on 10.0.1.21 uses PyYAML 5.3.1, which is "
                "vulnerable to CVE-2020-14343 (arbitrary code execution via "
                "yaml.load without SafeLoader). An attacker who controls YAML "
                "input can achieve remote code execution."
            ),
            "affected_asset": "10.0.1.21:8443",
            "cvss_score": 9.8,
            "cve_ids": ["CVE-2020-14343"],
            "remediation": (
                "Upgrade PyYAML to 6.0.1+. Replace all yaml.load() calls with "
                "yaml.safe_load(). Add safety-focused linting rules to prevent "
                "use of unsafe YAML loaders."
            ),
            "scanner_name": "sbom",
        },
    ]


def _cloud_findings() -> List[Dict]:
    """4 cloud infrastructure findings."""
    return [
        {
            "severity": "CRITICAL",
            "title": "S3 Bucket 'acme-backups' is Publicly Accessible",
            "description": (
                "The S3 bucket 'acme-backups' has a bucket policy that grants "
                "s3:GetObject to '*' (all AWS accounts and anonymous users). "
                "The bucket contains database backups, configuration files, and "
                "application logs that include PII and credentials."
            ),
            "affected_asset": "s3://acme-backups",
            "cvss_score": 9.1,
            "cve_ids": [],
            "remediation": (
                "Remove the public access policy immediately. Enable S3 Block "
                "Public Access at the account level. Rotate all credentials found "
                "in exposed backups. Enable S3 server-side encryption (SSE-S3 or "
                "SSE-KMS). Enable CloudTrail S3 data event logging."
            ),
            "scanner_name": "cloud",
        },
        {
            "severity": "HIGH",
            "title": "IAM User 'deploy-bot' Has AdministratorAccess Policy",
            "description": (
                "The IAM user 'deploy-bot' used by the CI/CD pipeline has the "
                "AWS-managed AdministratorAccess policy attached, granting full "
                "access to all AWS services. This violates the principle of least "
                "privilege and means a compromised CI/CD pipeline = full AWS "
                "account takeover."
            ),
            "affected_asset": "iam::deploy-bot",
            "cvss_score": 8.8,
            "cve_ids": [],
            "remediation": (
                "Replace AdministratorAccess with a scoped policy granting only "
                "the permissions needed for deployment (ECR push, ECS update, "
                "S3 write to deployment bucket). Use IAM Access Analyzer to "
                "identify the minimum required permissions."
            ),
            "scanner_name": "cloud",
        },
        {
            "severity": "HIGH",
            "title": "CloudTrail Logging Disabled in us-west-2",
            "description": (
                "AWS CloudTrail is not enabled in the us-west-2 region. An attacker "
                "who gains access to the account could perform actions in this region "
                "without any audit trail. Multi-region CloudTrail is required by "
                "most compliance frameworks (NIST AC-2, PCI DSS 10.1)."
            ),
            "affected_asset": "aws::us-west-2",
            "cvss_score": 7.5,
            "cve_ids": [],
            "remediation": (
                "Enable a multi-region CloudTrail trail that covers all AWS regions. "
                "Send logs to a centralized S3 bucket with MFA delete enabled. "
                "Enable CloudTrail Insights for anomaly detection."
            ),
            "scanner_name": "cloud",
        },
        {
            "severity": "MEDIUM",
            "title": "Security Group Allows SSH from 0.0.0.0/0",
            "description": (
                "Security group sg-0a1b2c3d allows inbound SSH (port 22) from "
                "0.0.0.0/0 (the entire internet). This security group is attached "
                "to the cloud jump host at 10.0.2.10. SSH brute-force attacks "
                "are continuous and automated."
            ),
            "affected_asset": "sg-0a1b2c3d",
            "cvss_score": 6.5,
            "cve_ids": [],
            "remediation": (
                "Restrict SSH inbound to known IP ranges (corporate egress IPs). "
                "Implement AWS Systems Manager Session Manager or EC2 Instance "
                "Connect for shell access, eliminating the need for open SSH ports."
            ),
            "scanner_name": "cloud",
        },
    ]


# ---------------------------------------------------------------------------
# NIST 800-53 compliance mapping — 65% coverage with specific gaps
# ---------------------------------------------------------------------------

NIST_CONTROLS_TOTAL = 20  # representative subset for demo

NIST_CONTROLS_COVERED = {
    "AC-2": {"name": "Account Management", "family": "Access Control", "status": "partial",
             "gap": "No automated account review process; shared service accounts found."},
    "AC-3": {"name": "Access Enforcement", "family": "Access Control", "status": "met"},
    "AC-6": {"name": "Least Privilege", "family": "Access Control", "status": "not_met",
             "gap": "IAM user deploy-bot has AdministratorAccess. 3 users have domain admin."},
    "AU-2": {"name": "Event Logging", "family": "Audit", "status": "partial",
             "gap": "CloudTrail disabled in us-west-2. No centralized log aggregation."},
    "AU-6": {"name": "Audit Record Review", "family": "Audit", "status": "partial",
             "gap": "Manual log review performed quarterly; no automated alerting or SIEM correlation."},
    "CA-2": {"name": "Control Assessments", "family": "Assessment", "status": "met"},
    "CM-2": {"name": "Baseline Configuration", "family": "Configuration Mgmt", "status": "met"},
    "CM-6": {"name": "Configuration Settings", "family": "Configuration Mgmt", "status": "partial",
             "gap": "SMBv1 enabled, default SNMP community strings, TLS 1.0/1.1 still active."},
    "IA-2": {"name": "Identification and Auth", "family": "Identification", "status": "met"},
    "IA-5": {"name": "Authenticator Management", "family": "Identification", "status": "met"},
    "IR-4": {"name": "Incident Handling", "family": "Incident Response", "status": "met"},
    "IR-6": {"name": "Incident Reporting", "family": "Incident Response", "status": "met"},
    "RA-3": {"name": "Risk Assessment", "family": "Risk Assessment", "status": "met"},
    "RA-5": {"name": "Vulnerability Monitoring and Scanning", "family": "Risk Assessment", "status": "met"},
    "SA-11": {"name": "Developer Testing", "family": "System Acquisition", "status": "not_met",
              "gap": "No SAST/DAST in CI/CD pipeline. Code review not required for merge."},
    "SC-7": {"name": "Boundary Protection", "family": "System Comms", "status": "partial",
             "gap": "Security group allows SSH from 0.0.0.0/0. Database accessible from full subnet."},
    "SC-8": {"name": "Transmission Confidentiality", "family": "System Comms", "status": "partial",
             "gap": "TLS 1.0/1.1 still enabled on mail server. Missing HSTS header."},
    "SC-28": {"name": "Protection of Information at Rest", "family": "System Comms", "status": "partial",
              "gap": "S3 bucket unencrypted. Database backups not encrypted at rest. Internal drives use BitLocker."},
    "SI-2": {"name": "Flaw Remediation", "family": "System Integrity", "status": "not_met",
             "gap": "Log4j 2.14.1 still deployed (2+ years unpatched). OpenSSL 1.1.1 EOL."},
    "SI-4": {"name": "System Monitoring", "family": "System Integrity", "status": "partial",
             "gap": "No IDS/IPS deployed. SIEM integration not configured."},
}

# AI analysis content
DEMO_AI_ANALYSIS = {
    "executive_summary": (
        "Acme Corp's security posture shows significant gaps in vulnerability management, "
        "access control, and cloud security. The most critical finding is a publicly "
        "accessible S3 bucket containing database backups with PII — this represents an "
        "active data breach risk requiring immediate remediation. The presence of Log4Shell "
        "in the CI/CD pipeline (2+ years unpatched) indicates a systemic failure in patch "
        "management processes."
    ),
    "risk_rating": "HIGH",
    "top_recommendations": [
        "IMMEDIATE (24h): Restrict S3 bucket 'acme-backups' access and rotate all exposed credentials.",
        "IMMEDIATE (24h): Patch OpenSSH on 10.0.2.10 to mitigate regreSSHion (CVE-2024-6387).",
        "URGENT (72h): Upgrade Log4j to 2.21.0+ across all Java applications.",
        "URGENT (72h): Scope down IAM deploy-bot permissions to least privilege.",
        "SHORT-TERM (30d): Disable SMBv1, TLS 1.0/1.1; enable LDAP signing on DCs.",
        "SHORT-TERM (30d): Deploy SAST/DAST in CI/CD pipeline; enable multi-region CloudTrail.",
        "MEDIUM-TERM (90d): Implement centralized log aggregation and automated review.",
    ],
    "compliance_impact": (
        "Current NIST 800-53 coverage is approximately 65%. Five controls are fully unmet "
        "(AC-6, AU-6, SA-11, SC-28, SI-2), and six are only partially met. The gaps in "
        "AU-6 (audit review) and SI-2 (flaw remediation) would be findings in any SOC 2 "
        "or FedRAMP audit. PCI DSS 4.0 compliance is at risk due to TLS 1.0/1.1 usage."
    ),
}


# ---------------------------------------------------------------------------
# Core functions
# ---------------------------------------------------------------------------

def generate_demo_findings() -> List[Dict]:
    """Return all demo findings across scan types."""
    findings = []
    findings.extend(_network_findings())
    findings.extend(_web_findings())
    findings.extend(_sbom_findings())
    findings.extend(_cloud_findings())
    return findings


def generate_demo_compliance() -> Dict:
    """Return NIST 800-53 compliance mapping with coverage stats."""
    met = sum(1 for c in NIST_CONTROLS_COVERED.values() if c["status"] == "met")
    partial = sum(1 for c in NIST_CONTROLS_COVERED.values() if c["status"] == "partial")
    not_met = sum(1 for c in NIST_CONTROLS_COVERED.values() if c["status"] == "not_met")
    total = len(NIST_CONTROLS_COVERED)
    # Coverage: met counts as 1.0, partial as 0.5
    coverage_pct = round((met + partial * 0.5) / total * 100)

    return {
        "framework": "nist_800_53",
        "framework_label": "NIST 800-53 Rev. 5",
        "total_controls_assessed": total,
        "coverage_percent": coverage_pct,
        "controls_met": met,
        "controls_partial": partial,
        "controls_not_met": not_met,
        "controls": NIST_CONTROLS_COVERED,
        "gaps": {
            cid: ctrl["gap"]
            for cid, ctrl in NIST_CONTROLS_COVERED.items()
            if ctrl.get("gap")
        },
    }


def _populate_demo_session(em, session_id: str, findings_data: List[Dict],
                           compliance_data: Dict) -> None:
    """Insert demo findings and compliance mappings into the evidence DB."""
    now = datetime.now(timezone.utc)

    for i, f in enumerate(findings_data):
        # Add evidence artifact for each finding
        evidence_id = em.add_evidence(
            session_id=session_id,
            evidence_type="scan_result",
            title=f["title"],
            description=f["description"],
            source_tool=f.get("scanner_name", "demo"),
            raw_data=f,
        )

        # Add the finding
        em.add_finding(
            session_id=session_id,
            severity=f["severity"],
            title=f["title"],
            description=f["description"],
            affected_asset=f["affected_asset"],
            cvss_score=f["cvss_score"],
            cve_ids=f.get("cve_ids", []),
            remediation=f.get("remediation", ""),
            evidence_id=evidence_id,
            scanner_name=f.get("scanner_name", "demo"),
            metadata={"demo": True, "scanner": f.get("scanner_name", "demo")},
        )

        # Map findings to NIST controls where relevant
        _map_finding_to_controls(em, evidence_id, f)

    # Add compliance assessment evidence
    compliance_evidence_id = em.add_evidence(
        session_id=session_id,
        evidence_type="compliance_assessment",
        title="NIST 800-53 Rev. 5 Assessment",
        description=f"Coverage: {compliance_data['coverage_percent']}%",
        source_tool="compliance",
        raw_data=compliance_data,
    )

    # Map each assessed control
    for control_id, ctrl in compliance_data["controls"].items():
        em.map_to_control(
            evidence_id=compliance_evidence_id,
            framework="nist_800_53",
            control_id=control_id,
            control_name=ctrl["name"],
            control_family=ctrl["family"],
            mapping_type="assessed",
            notes=ctrl.get("gap", f"Control {ctrl['status']}"),
        )

    # Add AI analysis as evidence
    em.add_evidence(
        session_id=session_id,
        evidence_type="ai_analysis",
        title="AI-Powered Risk Analysis",
        description=DEMO_AI_ANALYSIS["executive_summary"],
        source_tool="ai_engine",
        raw_data=DEMO_AI_ANALYSIS,
    )

    # End the session
    summary = {
        "total_findings": len(findings_data),
        "severity_counts": {
            "CRITICAL": sum(1 for f in findings_data if f["severity"] == "CRITICAL"),
            "HIGH": sum(1 for f in findings_data if f["severity"] == "HIGH"),
            "MEDIUM": sum(1 for f in findings_data if f["severity"] == "MEDIUM"),
            "LOW": sum(1 for f in findings_data if f["severity"] == "LOW"),
        },
        "hosts_discovered": len(DEMO_HOSTS),
        "open_ports": len(DEMO_OPEN_PORTS),
        "compliance_coverage": compliance_data["coverage_percent"],
        "target_org": "Acme Corp",
        "demo_mode": True,
    }
    em.end_session(session_id, summary=summary)


def _map_finding_to_controls(em, evidence_id: str, finding: Dict) -> None:
    """Map a finding to relevant NIST 800-53 controls."""
    title_lower = finding["title"].lower()
    mappings = []

    if "ssh" in title_lower or "rdp" in title_lower:
        mappings.append(("AC-2", "Account Management", "Access Control"))
        mappings.append(("SC-7", "Boundary Protection", "System Comms"))
    if "sql injection" in title_lower:
        mappings.append(("SA-11", "Developer Testing", "System Acquisition"))
        mappings.append(("SI-10", "Information Input Validation", "System Integrity"))
    if "xss" in title_lower or "cross-site" in title_lower:
        mappings.append(("SA-11", "Developer Testing", "System Acquisition"))
    if "tls" in title_lower or "ssl" in title_lower or "header" in title_lower:
        mappings.append(("SC-8", "Transmission Confidentiality", "System Comms"))
    if "smb" in title_lower or "ldap" in title_lower:
        mappings.append(("CM-6", "Configuration Settings", "Configuration Mgmt"))
    if "s3" in title_lower or "bucket" in title_lower:
        mappings.append(("SC-28", "Protection of Information at Rest", "System Comms"))
        mappings.append(("AC-3", "Access Enforcement", "Access Control"))
    if "iam" in title_lower or "privilege" in title_lower:
        mappings.append(("AC-6", "Least Privilege", "Access Control"))
    if "cloudtrail" in title_lower or "logging" in title_lower:
        mappings.append(("AU-2", "Event Logging", "Audit"))
    if "log4j" in title_lower or "outdated" in title_lower or "vulnerable" in title_lower:
        mappings.append(("SI-2", "Flaw Remediation", "System Integrity"))
    if "security group" in title_lower:
        mappings.append(("SC-7", "Boundary Protection", "System Comms"))
    if "snmp" in title_lower:
        mappings.append(("CM-6", "Configuration Settings", "Configuration Mgmt"))
    if "directory listing" in title_lower:
        mappings.append(("CM-6", "Configuration Settings", "Configuration Mgmt"))
    if "version disclosed" in title_lower:
        mappings.append(("CM-6", "Configuration Settings", "Configuration Mgmt"))
    if "postgresql" in title_lower or "database" in title_lower:
        mappings.append(("SC-7", "Boundary Protection", "System Comms"))

    for control_id, control_name, family in mappings:
        em.map_to_control(
            evidence_id=evidence_id,
            framework="nist_800_53",
            control_id=control_id,
            control_name=control_name,
            control_family=family,
            mapping_type="supports",
            notes=f"Finding: {finding['title']}",
        )


def generate_demo_report(session_id: str) -> str:
    """Generate the self-contained HTML report for the demo session.

    Returns the path to the generated report file.
    """
    html = generate_interactive_report(session_id)
    # Also write to a convenient location in the project root
    report_path = paths.home / "demo-report.html"
    report_path.write_text(html, encoding="utf-8")
    return str(report_path)


def _print_banner(report_path: str) -> None:
    """Print the demo mode banner."""
    try:
        version = Config().version
    except Exception:
        version = "7.5.0"

    # Determine actual widths for clean box drawing
    lines = [
        f"  DONJON PLATFORM v{version} -- DEMO MODE",
        "",
        "  Dashboard: http://localhost:8443",
        f"  Report:    {report_path}",
        "",
        '  This is simulated data for "Acme Corp"',
        "  To scan your own environment: donjon scan --quick",
        "",
        "  Get started: https://donjonsec.com/download",
    ]
    width = max(len(line) for line in lines) + 4

    print()
    try:
        print("\033[0;35m", end="")  # purple
        print(f"\u2554{'\u2550' * width}\u2557")
        for line in lines:
            print(f"\u2551{line:<{width}}\u2551")
        print(f"\u255a{'\u2550' * width}\u255d")
        print("\033[0m", end="")  # reset
    except UnicodeEncodeError:
        # Fallback for Windows terminals that can't render box-drawing chars
        print("+" + "=" * width + "+")
        for line in lines:
            print(f"| {line:<{width - 1}}|")
        print("+" + "=" * width + "+")
    print()


def run_demo() -> None:
    """Main entry point for demo mode.

    1. Create demo session with realistic findings
    2. Generate self-contained HTML report
    3. Start dashboard web server in background
    4. Open report in browser
    5. Print banner
    """
    print("\n  [*] Generating demo data for Acme Corp...")

    em = get_evidence_manager()

    # Start a demo session
    session_id = em.start_session(
        scan_type="deep",
        target_networks=DEMO_SESSION_META["target_networks"],
        metadata={"demo": True, "target_org": "Acme Corp"},
    )

    # Generate and populate findings
    findings = generate_demo_findings()
    compliance = generate_demo_compliance()
    _populate_demo_session(em, session_id, findings, compliance)
    print(f"  [OK] Created {len(findings)} findings across 4 scan types.")
    print(f"  [OK] NIST 800-53 compliance: {compliance['coverage_percent']}% coverage.")

    # Generate the standalone HTML report
    print("  [*] Generating self-contained HTML report...")
    report_path = generate_demo_report(session_id)
    print(f"  [OK] Report saved to {report_path}")

    # Start the web server in a background thread
    print("  [*] Starting dashboard server on http://localhost:8443 ...")

    def _start_server():
        try:
            sys.path.insert(0, str(paths.home))
            from web.api import start_server
            start_server(host='127.0.0.1', port=8443, no_auth=True, prefer_flask=True)
        except Exception as e:
            print(f"\n  [WARN] Dashboard server failed to start: {e}")
            print("         The HTML report is still available at the path above.")

    server_thread = threading.Thread(target=_start_server, daemon=True)
    server_thread.start()

    # Give the server a moment to bind
    time.sleep(1.5)

    # Open the report in the browser
    try:
        webbrowser.open(f"file:///{report_path.replace(os.sep, '/')}")
    except Exception:
        pass  # non-critical

    # Print the banner
    _print_banner(report_path)

    print("  Press Ctrl+C to stop the dashboard server.\n")

    # Keep running until Ctrl+C
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("\n  [*] Demo mode stopped.")


if __name__ == "__main__":
    run_demo()
