#!/usr/bin/env python3
"""
SAGE Code Scanner — Multi-Layer Security & Quality Analysis
Powered by Eden's Intelligence

Layers:
  1. Pattern matching (instant)
  2. AST structural analysis (syntax, complexity, docstrings, type hints)
  3. Bandit security scan (CVE-grade Python vulnerabilities)
  4. Radon complexity metrics (cyclomatic + maintainability)
  5. Eden LLM deep review (ollama — the differentiator)

φ = 1.618 | RETIRE_DADDY
"""
import ast
import json
import os
import re
import subprocess
import tempfile
import time
import traceback
from datetime import datetime

import requests
from flask import Flask, request, jsonify, Response

app = Flask(__name__)

OLLAMA_URL = "http://localhost:11434/api/generate"
LLM_MODEL = "eden-coder-omega-v3:latest"
LLM_TIMEOUT = 90
PHI = 1.618033988749895

# ═══════════════════════════════════════════════════════════
# Layer 1: Pattern Matching (all languages)
# ═══════════════════════════════════════════════════════════
SECURITY_PATTERNS = {
    # Injection
    "eval(": {"risk": "Code Injection", "severity": "HIGH", "cwe": "CWE-94"},
    "exec(": {"risk": "Code Injection", "severity": "HIGH", "cwe": "CWE-94"},
    "os.system": {"risk": "Command Injection", "severity": "CRITICAL", "cwe": "CWE-78"},
    "subprocess.call": {"risk": "Command Execution", "severity": "HIGH", "cwe": "CWE-78"},
    "shell=True": {"risk": "Shell Injection", "severity": "CRITICAL", "cwe": "CWE-78"},
    "__import__": {"risk": "Dynamic Import", "severity": "MEDIUM", "cwe": "CWE-502"},
    # Deserialization
    "pickle.loads": {"risk": "Unsafe Deserialization", "severity": "HIGH", "cwe": "CWE-502"},
    "yaml.load(": {"risk": "Unsafe YAML Load", "severity": "HIGH", "cwe": "CWE-502"},
    "marshal.loads": {"risk": "Unsafe Deserialization", "severity": "HIGH", "cwe": "CWE-502"},
    # Crypto
    "md5": {"risk": "Weak Hash (MD5)", "severity": "MEDIUM", "cwe": "CWE-328"},
    "sha1": {"risk": "Weak Hash (SHA1)", "severity": "LOW", "cwe": "CWE-328"},
    "DES": {"risk": "Weak Cipher (DES)", "severity": "HIGH", "cwe": "CWE-327"},
    # SQL
    "execute(\"": {"risk": "Possible SQL Injection", "severity": "HIGH", "cwe": "CWE-89"},
    "execute('": {"risk": "Possible SQL Injection", "severity": "HIGH", "cwe": "CWE-89"},
    "f\"SELECT": {"risk": "SQL Injection via f-string", "severity": "CRITICAL", "cwe": "CWE-89"},
    "f'SELECT": {"risk": "SQL Injection via f-string", "severity": "CRITICAL", "cwe": "CWE-89"},
    # Secrets
    "password =": {"risk": "Hardcoded Password", "severity": "MEDIUM", "cwe": "CWE-798"},
    "api_key =": {"risk": "Hardcoded API Key", "severity": "MEDIUM", "cwe": "CWE-798"},
    "secret =": {"risk": "Hardcoded Secret", "severity": "MEDIUM", "cwe": "CWE-798"},
    # Web
    "mark_safe": {"risk": "XSS via mark_safe", "severity": "HIGH", "cwe": "CWE-79"},
    "| safe": {"risk": "XSS via safe filter", "severity": "HIGH", "cwe": "CWE-79"},
    "innerHTML": {"risk": "XSS via innerHTML", "severity": "HIGH", "cwe": "CWE-79"},
    # File
    "open(": {"risk": "File Operation (check path validation)", "severity": "INFO", "cwe": "CWE-22"},
    # Rust/systems
    "unsafe {": {"risk": "Unsafe Rust Block", "severity": "MEDIUM", "cwe": "CWE-119"},
    "saturating_add": {"risk": "Potential Gas Metering Bypass", "severity": "HIGH", "cwe": "N/A"},
    "unchecked_math": {"risk": "Arithmetic Overflow Risk", "severity": "HIGH", "cwe": "CWE-190"},
}

def layer1_patterns(code):
    """Fast pattern scan across all languages."""
    findings = []
    lines = code.split("\n")
    for i, line in enumerate(lines, 1):
        stripped = line.strip()
        if stripped.startswith("#") or stripped.startswith("//"):
            continue
        for pattern, info in SECURITY_PATTERNS.items():
            if pattern in line:
                if info["severity"] == "INFO":
                    continue
                findings.append({
                    "line": i,
                    "pattern": pattern,
                    "risk": info["risk"],
                    "severity": info["severity"],
                    "cwe": info["cwe"],
                    "code": stripped[:120],
                    "layer": "pattern_match"
                })
    return findings


# ═══════════════════════════════════════════════════════════
# Layer 2: AST Analysis (Python only)
# ═══════════════════════════════════════════════════════════
def layer2_ast(code, lang):
    """Structural analysis via AST (Python only)."""
    if lang != "python":
        return {"available": False, "reason": f"AST analysis not available for {lang}"}

    result = {
        "available": True,
        "syntax_valid": False,
        "functions": 0,
        "classes": 0,
        "imports": 0,
        "lines": len(code.split("\n")),
        "issues": [],
        "suggestions": [],
    }

    try:
        tree = ast.parse(code)
        result["syntax_valid"] = True
    except SyntaxError as e:
        result["issues"].append({
            "type": "syntax_error",
            "message": f"{e.msg} at line {e.lineno}",
            "severity": "CRITICAL",
            "line": e.lineno
        })
        return result

    funcs = [n for n in ast.walk(tree) if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))]
    classes = [n for n in ast.walk(tree) if isinstance(n, ast.ClassDef)]
    imports = [n for n in ast.walk(tree) if isinstance(n, (ast.Import, ast.ImportFrom))]

    result["functions"] = len(funcs)
    result["classes"] = len(classes)
    result["imports"] = len(imports)

    # Missing docstrings
    missing_docs = []
    for func in funcs:
        if not ast.get_docstring(func):
            missing_docs.append(func.name)
    if missing_docs:
        result["suggestions"].append({
            "type": "missing_docstrings",
            "functions": missing_docs[:10],
            "count": len(missing_docs)
        })

    # Missing type hints
    missing_hints = []
    for func in funcs:
        has_hints = func.returns is not None or any(
            arg.annotation is not None for arg in func.args.args
        )
        if not has_hints:
            missing_hints.append(func.name)
    if missing_hints:
        result["suggestions"].append({
            "type": "missing_type_hints",
            "functions": missing_hints[:10],
            "count": len(missing_hints)
        })

    # Long functions
    for func in funcs:
        try:
            func_code = ast.unparse(func)
            func_lines = func_code.count("\n") + 1
            if func_lines > 40:
                result["issues"].append({
                    "type": "long_function",
                    "function": func.name,
                    "lines": func_lines,
                    "severity": "LOW",
                    "message": f"Function '{func.name}' is {func_lines} lines — consider refactoring"
                })
        except:
            pass

    # Global variables
    global_assigns = [n for n in ast.iter_child_nodes(tree) if isinstance(n, ast.Assign)]
    if len(global_assigns) > 10:
        result["issues"].append({
            "type": "excessive_globals",
            "count": len(global_assigns),
            "severity": "LOW",
            "message": f"{len(global_assigns)} global assignments — consider using classes or modules"
        })

    # Bare except
    for node in ast.walk(tree):
        if isinstance(node, ast.ExceptHandler) and node.type is None:
            result["issues"].append({
                "type": "bare_except",
                "line": node.lineno,
                "severity": "MEDIUM",
                "message": f"Bare 'except:' at line {node.lineno} — catches all exceptions including KeyboardInterrupt"
            })

    return result


# ═══════════════════════════════════════════════════════════
# Layer 3: Bandit Security Scan (Python only)
# ═══════════════════════════════════════════════════════════
def layer3_bandit(code, lang):
    """Run bandit security scanner."""
    if lang != "python":
        return {"available": False, "reason": f"Bandit only supports Python"}

    try:
        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
            f.write(code)
            tmp_path = f.name

        result = subprocess.run(
            ["bandit", "-f", "json", "-q", tmp_path],
            capture_output=True, text=True, timeout=30
        )
        os.unlink(tmp_path)

        if result.stdout:
            data = json.loads(result.stdout)
            findings = []
            for r in data.get("results", []):
                findings.append({
                    "test_id": r.get("test_id", ""),
                    "test_name": r.get("test_name", ""),
                    "severity": r.get("issue_severity", "UNKNOWN"),
                    "confidence": r.get("issue_confidence", "UNKNOWN"),
                    "risk": r.get("issue_text", ""),
                    "line": r.get("line_number", 0),
                    "cwe": f"CWE-{r['issue_cwe']['id']}" if r.get("issue_cwe") else "N/A",
                    "code": r.get("code", "").strip()[:200],
                    "layer": "bandit"
                })

            metrics = data.get("metrics", {}).get(tmp_path, data.get("metrics", {}).get("_totals", {}))
            return {
                "available": True,
                "findings": findings,
                "severity_counts": {
                    "high": metrics.get("SEVERITY.HIGH", 0),
                    "medium": metrics.get("SEVERITY.MEDIUM", 0),
                    "low": metrics.get("SEVERITY.LOW", 0),
                },
                "confidence_counts": {
                    "high": metrics.get("CONFIDENCE.HIGH", 0),
                    "medium": metrics.get("CONFIDENCE.MEDIUM", 0),
                    "low": metrics.get("CONFIDENCE.LOW", 0),
                }
            }
        return {"available": True, "findings": [], "severity_counts": {}, "confidence_counts": {}}

    except subprocess.TimeoutExpired:
        try: os.unlink(tmp_path)
        except: pass
        return {"available": False, "reason": "Bandit timed out"}
    except Exception as e:
        try: os.unlink(tmp_path)
        except: pass
        return {"available": False, "reason": str(e)}


# ═══════════════════════════════════════════════════════════
# Layer 4: Radon Complexity Analysis (Python only)
# ═══════════════════════════════════════════════════════════
def layer4_radon(code, lang):
    """Run radon cyclomatic complexity + maintainability analysis."""
    if lang != "python":
        return {"available": False, "reason": f"Radon only supports Python"}

    try:
        with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
            f.write(code)
            tmp_path = f.name

        # Cyclomatic complexity
        cc_result = subprocess.run(
            ["radon", "cc", "-j", "-a", tmp_path],
            capture_output=True, text=True, timeout=15
        )

        # Maintainability index
        mi_result = subprocess.run(
            ["radon", "mi", "-j", tmp_path],
            capture_output=True, text=True, timeout=15
        )

        os.unlink(tmp_path)

        cc_data = {}
        mi_data = {}
        try: cc_data = json.loads(cc_result.stdout) if cc_result.stdout else {}
        except: pass
        try: mi_data = json.loads(mi_result.stdout) if mi_result.stdout else {}
        except: pass

        functions = []
        for path, items in cc_data.items():
            if isinstance(items, list):
                for item in items:
                    if isinstance(item, dict):
                        functions.append({
                            "name": item.get("name", "?"),
                            "type": item.get("type", "?"),
                            "complexity": item.get("complexity", 0),
                            "rank": item.get("rank", "?"),
                            "line": item.get("lineno", 0),
                        })

        mi_score = None
        mi_rank = None
        for path, score in mi_data.items():
            if isinstance(score, dict):
                mi_score = score.get("mi", None)
                mi_rank = score.get("rank", None)
            elif isinstance(score, (int, float)):
                mi_score = score

        complex_funcs = [f for f in functions if f["complexity"] > 10]

        return {
            "available": True,
            "maintainability_index": round(mi_score, 1) if mi_score else None,
            "maintainability_rank": mi_rank,
            "functions_analyzed": len(functions),
            "complex_functions": complex_funcs,
            "avg_complexity": round(
                sum(f["complexity"] for f in functions) / max(len(functions), 1), 1
            ),
            "max_complexity": max((f["complexity"] for f in functions), default=0),
        }

    except Exception as e:
        try: os.unlink(tmp_path)
        except: pass
        return {"available": False, "reason": str(e)}


# ═══════════════════════════════════════════════════════════
# Layer 5: Eden LLM Deep Review
# ═══════════════════════════════════════════════════════════
def layer5_llm(code, lang, findings_summary=""):
    """Use Eden's LLM for deep code review."""
    prompt = f"""You are SAGE, a senior code security and quality auditor. Review this {lang} code.

Previous automated scans found: {findings_summary or 'nothing notable'}

Provide a focused review covering:
1. Security vulnerabilities the pattern scanner might have missed
2. Logic errors or edge cases
3. Performance concerns
4. Architecture suggestions
5. One specific, actionable improvement with a code example

Be concise and specific. No generic advice. Reference actual line content.

```{lang}
{code[:4000]}
```

REVIEW:"""

    try:
        resp = requests.post(OLLAMA_URL, json={
            "model": LLM_MODEL,
            "prompt": prompt,
            "stream": False,
            "options": {"num_predict": 800, "temperature": 0.3}
        }, timeout=LLM_TIMEOUT)

        if resp.ok:
            text = resp.json().get("response", "").strip()
            # Clean response
            text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL).strip()
            return {"available": True, "review": text}
        return {"available": False, "reason": f"Ollama returned {resp.status_code}"}
    except requests.Timeout:
        return {"available": False, "reason": "LLM review timed out (90s)"}
    except Exception as e:
        return {"available": False, "reason": str(e)}


# ═══════════════════════════════════════════════════════════
# Grading
# ═══════════════════════════════════════════════════════════
def compute_grade(pattern_findings, bandit_data, ast_data, radon_data):
    """Compute overall grade A-F."""
    score = 100

    # Pattern findings
    for f in pattern_findings:
        if f["severity"] == "CRITICAL": score -= 20
        elif f["severity"] == "HIGH": score -= 12
        elif f["severity"] == "MEDIUM": score -= 6

    # Bandit findings
    if bandit_data.get("available"):
        for f in bandit_data.get("findings", []):
            if f["severity"] == "HIGH": score -= 15
            elif f["severity"] == "MEDIUM": score -= 8
            elif f["severity"] == "LOW": score -= 3

    # AST issues
    if ast_data.get("available"):
        for iss in ast_data.get("issues", []):
            if iss["severity"] == "CRITICAL": score -= 20
            elif iss["severity"] == "MEDIUM": score -= 5
            elif iss["severity"] == "LOW": score -= 2

    # Radon complexity
    if radon_data.get("available"):
        mi = radon_data.get("maintainability_index")
        if mi is not None:
            if mi < 10: score -= 20
            elif mi < 20: score -= 10
        if radon_data.get("max_complexity", 0) > 20:
            score -= 10

    score = max(0, min(100, score))

    if score >= 90: grade = "A"
    elif score >= 75: grade = "B"
    elif score >= 60: grade = "C"
    elif score >= 40: grade = "D"
    else: grade = "F"

    return grade, score


# ═══════════════════════════════════════════════════════════
# API Routes
# ═══════════════════════════════════════════════════════════

@app.route("/health")
def health():
    return jsonify({
        "status": "SAGE Online",
        "version": "3.0",
        "layers": ["pattern_match", "ast_analysis", "bandit", "radon", "eden_llm"],
        "timestamp": datetime.now().isoformat()
    })


@app.route("/api/review", methods=["POST"])
def api_review():
    """Quick review (layers 1-4, no LLM)."""
    data = request.json or {}
    code = data.get("code", "")
    lang = data.get("lang", "python").lower()

    if not code:
        return jsonify({"error": "No code provided"}), 400
    if len(code) > 100_000:
        return jsonify({"error": "Code too large (max 100KB)"}), 400

    t0 = time.time()
    patterns = layer1_patterns(code)
    ast_data = layer2_ast(code, lang)
    bandit_data = layer3_bandit(code, lang)
    radon_data = layer4_radon(code, lang)
    grade, score = compute_grade(patterns, bandit_data, ast_data, radon_data)

    return jsonify({
        "status": "success",
        "scan_time_ms": round((time.time() - t0) * 1000),
        "grade": grade,
        "score": score,
        "language": lang,
        "lines": len(code.split("\n")),
        "layers": {
            "pattern_match": {"findings": patterns},
            "ast_analysis": ast_data,
            "bandit": bandit_data,
            "radon": radon_data,
        }
    })


@app.route("/api/deep-review", methods=["POST"])
def api_deep_review():
    """Full review including LLM analysis (layers 1-5)."""
    data = request.json or {}
    code = data.get("code", "")
    lang = data.get("lang", "python").lower()

    if not code:
        return jsonify({"error": "No code provided"}), 400
    if len(code) > 100_000:
        return jsonify({"error": "Code too large (max 100KB)"}), 400

    t0 = time.time()
    patterns = layer1_patterns(code)
    ast_data = layer2_ast(code, lang)
    bandit_data = layer3_bandit(code, lang)
    radon_data = layer4_radon(code, lang)
    grade, score = compute_grade(patterns, bandit_data, ast_data, radon_data)

    # Build summary for LLM
    summary_parts = []
    if patterns:
        summary_parts.append(f"{len(patterns)} pattern matches ({', '.join(set(f['risk'] for f in patterns[:5]))})")
    if bandit_data.get("findings"):
        summary_parts.append(f"{len(bandit_data['findings'])} bandit findings")
    if radon_data.get("complex_functions"):
        summary_parts.append(f"{len(radon_data['complex_functions'])} high-complexity functions")
    summary = "; ".join(summary_parts) if summary_parts else "no automated findings"

    llm_data = layer5_llm(code, lang, summary)

    return jsonify({
        "status": "success",
        "scan_time_ms": round((time.time() - t0) * 1000),
        "grade": grade,
        "score": score,
        "language": lang,
        "lines": len(code.split("\n")),
        "layers": {
            "pattern_match": {"findings": patterns},
            "ast_analysis": ast_data,
            "bandit": bandit_data,
            "radon": radon_data,
            "eden_llm": llm_data,
        }
    })


# ═══════════════════════════════════════════════════════════
# Landing Page
# ═══════════════════════════════════════════════════════════
LANDING_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SAGE — AI Code Security Scanner</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
  --bg: #0c0e12;
  --surface: #141820;
  --surface2: #1a1f2a;
  --border: #2a3040;
  --accent: #22d97f;
  --accent2: #1bb86a;
  --danger: #ff4d6a;
  --warn: #ffb347;
  --info: #4da6ff;
  --text: #e4e8f0;
  --muted: #6b7a94;
  --code-bg: #0a0c10;
}
* { margin:0; padding:0; box-sizing:border-box; }
body { background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; min-height:100vh; }

.header {
  padding: 2rem 2rem 1rem;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
}
.logo { font-family: 'JetBrains Mono', monospace; font-size: 1.5rem; font-weight: 700; color: var(--accent); letter-spacing: 2px; }
.logo span { color: var(--muted); font-weight: 400; font-size: 0.7em; margin-left: 0.5em; }
.badge { font-size: 0.65rem; background: var(--accent); color: #000; padding: 2px 8px; border-radius: 3px; font-weight: 700; letter-spacing: 1px; }

.hero {
  max-width: 900px; margin: 0 auto; padding: 3rem 2rem 1rem;
  text-align: center;
}
.hero h1 { font-family: 'JetBrains Mono', monospace; font-size: 2rem; margin-bottom: 0.5rem; }
.hero h1 em { color: var(--accent); font-style: normal; }
.hero p { color: var(--muted); font-size: 1rem; max-width: 600px; margin: 0 auto 2rem; line-height: 1.6; }

.layers-bar {
  display: flex; justify-content: center; gap: 0.5rem; margin-bottom: 2rem; flex-wrap: wrap;
}
.layer-chip {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem; padding: 4px 10px; border-radius: 3px;
  border: 1px solid var(--border); color: var(--muted);
}
.layer-chip.active { border-color: var(--accent); color: var(--accent); }

.main { max-width: 900px; margin: 0 auto; padding: 0 2rem 4rem; }

.editor-wrap {
  background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
  overflow: hidden; margin-bottom: 1rem;
}
.editor-bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.5rem 1rem; background: var(--surface2); border-bottom: 1px solid var(--border);
}
.editor-bar select {
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  padding: 4px 8px; border-radius: 3px; font-family: 'JetBrains Mono', monospace; font-size: 0.8rem;
}
.editor-bar label { color: var(--muted); font-size: 0.8rem; display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }
.editor-bar input[type=checkbox] { accent-color: var(--accent); }
textarea {
  width: 100%; min-height: 300px; padding: 1rem;
  background: var(--code-bg); color: var(--text); border: none; outline: none;
  font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; line-height: 1.6; resize: vertical;
}
.btn {
  width: 100%; padding: 0.8rem; border: none; border-radius: 6px; cursor: pointer;
  font-family: 'JetBrains Mono', monospace; font-size: 1rem; font-weight: 700;
  background: var(--accent); color: #000; letter-spacing: 1px;
  transition: all 0.15s;
}
.btn:hover { background: var(--accent2); }
.btn:disabled { opacity: 0.4; cursor: wait; }

#results { margin-top: 2rem; display: none; }
.result-header {
  display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem;
  padding: 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
}
.grade-circle {
  width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-family: 'JetBrains Mono', monospace; font-size: 1.8rem; font-weight: 700; flex-shrink: 0;
}
.grade-A { background: #22d97f22; border: 2px solid var(--accent); color: var(--accent); }
.grade-B { background: #4da6ff22; border: 2px solid var(--info); color: var(--info); }
.grade-C { background: #ffb34722; border: 2px solid var(--warn); color: var(--warn); }
.grade-D { background: #ff4d6a22; border: 2px solid var(--danger); color: var(--danger); }
.grade-F { background: #ff4d6a33; border: 2px solid var(--danger); color: var(--danger); }
.result-meta { font-size: 0.85rem; color: var(--muted); }
.result-meta strong { color: var(--text); }

.findings-section { margin-bottom: 1.5rem; }
.section-title {
  font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: var(--accent);
  margin-bottom: 0.5rem; letter-spacing: 1px; text-transform: uppercase;
}
.finding {
  padding: 0.6rem 0.8rem; margin-bottom: 4px; border-radius: 4px;
  font-size: 0.8rem; font-family: 'JetBrains Mono', monospace;
  background: var(--surface); border-left: 3px solid var(--border);
}
.finding.CRITICAL { border-left-color: var(--danger); }
.finding.HIGH { border-left-color: #ff6b6b; }
.finding.MEDIUM { border-left-color: var(--warn); }
.finding.LOW { border-left-color: var(--info); }
.sev { font-weight: 700; font-size: 0.7rem; margin-right: 0.5rem; }
.sev.CRITICAL { color: var(--danger); }
.sev.HIGH { color: #ff6b6b; }
.sev.MEDIUM { color: var(--warn); }
.sev.LOW { color: var(--info); }

.llm-review {
  background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
  padding: 1rem; font-size: 0.85rem; line-height: 1.7; white-space: pre-wrap;
  font-family: 'DM Sans', sans-serif;
}

.pricing {
  margin-top: 3rem; padding: 2rem; background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
}
.pricing h3 { font-family: 'JetBrains Mono', monospace; color: var(--accent); margin-bottom: 1rem; }
.price-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; }
.price-card {
  padding: 1rem; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px;
}
.price-card h4 { font-size: 0.9rem; margin-bottom: 0.3rem; }
.price-card .price { color: var(--accent); font-family: 'JetBrains Mono', monospace; font-size: 1.2rem; font-weight: 700; }
.price-card p { color: var(--muted); font-size: 0.75rem; margin-top: 0.3rem; line-height: 1.4; }

.footer { text-align: center; padding: 2rem; color: var(--muted); font-size: 0.75rem; border-top: 1px solid var(--border); }
</style>
</head>
<body>

<div class="header">
  <div class="logo">SAGE<span>by Eden</span></div>
  <div class="badge">AI-POWERED</div>
</div>

<div class="hero">
  <h1>5-Layer <em>Code Security</em> Scanner</h1>
  <p>Paste your code. Get a security audit powered by static analysis, AST parsing, Bandit CVE detection, complexity metrics, and Eden's AI review.</p>
  <div class="layers-bar">
    <div class="layer-chip active">Pattern Match</div>
    <div class="layer-chip active">AST Analysis</div>
    <div class="layer-chip active">Bandit CVE</div>
    <div class="layer-chip active">Radon Complexity</div>
    <div class="layer-chip active">Eden AI Review</div>
  </div>
</div>

<div class="main">
  <div class="editor-wrap">
    <div class="editor-bar">
      <select id="lang">
        <option value="python" selected>Python</option>
        <option value="rust">Rust</option>
        <option value="js">JavaScript</option>
        <option value="java">Java</option>
        <option value="go">Go</option>
      </select>
      <label><input type="checkbox" id="deepToggle" checked> Include AI Review</label>
    </div>
    <textarea id="code" placeholder="Paste your code here...

# Example:
import os
def run_cmd(user_input):
    os.system(user_input)  # Command injection
    data = eval(user_input)  # Code injection
    return data
"></textarea>
  </div>
  <button class="btn" id="scanBtn" onclick="scan()">SCAN CODE</button>

  <div id="results"></div>

  <div class="pricing">
    <h3>// SAGE Products</h3>
    <div class="price-grid">
      <div class="price-card">
        <h4>Single File Review</h4>
        <div class="price">$25</div>
        <p>Security, performance & quality analysis of one file</p>
      </div>
      <div class="price-card">
        <h4>PR Review</h4>
        <div class="price">$50</div>
        <p>Full pull request analysis with line-by-line comments</p>
      </div>
      <div class="price-card">
        <h4>Security Scan</h4>
        <div class="price">$100</div>
        <p>OWASP Top 10 vulnerability scan + report</p>
      </div>
      <div class="price-card">
        <h4>Full Repo Audit</h4>
        <div class="price">$200</div>
        <p>Complete codebase — architecture, security, tech debt</p>
      </div>
    </div>
    <p style="color:var(--muted); font-size:0.75rem; margin-top:1rem;">Contact: jameyecho@gmail.com | PayPal: paypal.me/jamlen</p>
  </div>
</div>

<div class="footer">SAGE v3.0 — Powered by Eden | scanner.edensages.org</div>

<script>
async function scan() {
  const btn = document.getElementById('scanBtn');
  const code = document.getElementById('code').value;
  const lang = document.getElementById('lang').value;
  const deep = document.getElementById('deepToggle').checked;

  if (!code.trim()) return;

  btn.disabled = true;
  btn.textContent = deep ? 'SCANNING (5 LAYERS)...' : 'SCANNING (4 LAYERS)...';

  try {
    const endpoint = deep ? '/api/deep-review' : '/api/review';
    const resp = await fetch(endpoint, {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({code, lang})
    });
    const data = await resp.json();
    renderResults(data, deep);
  } catch(e) {
    document.getElementById('results').innerHTML = '<p style="color:var(--danger)">Scan failed: ' + e.message + '</p>';
    document.getElementById('results').style.display = 'block';
  }

  btn.disabled = false;
  btn.textContent = 'SCAN CODE';
}

function renderResults(data, deep) {
  const el = document.getElementById('results');
  let html = '';

  // Header
  const g = data.grade || '?';
  html += `<div class="result-header">
    <div class="grade-circle grade-${g}">${g}</div>
    <div class="result-meta">
      <strong>Score: ${data.score}/100</strong> &nbsp; Language: ${data.language} &nbsp; Lines: ${data.lines}<br>
      Scan time: ${data.scan_time_ms}ms
    </div>
  </div>`;

  const layers = data.layers || {};

  // Pattern findings
  const pf = (layers.pattern_match || {}).findings || [];
  if (pf.length) {
    html += '<div class="findings-section"><div class="section-title">Pattern Match</div>';
    pf.forEach(f => {
      html += `<div class="finding ${f.severity}"><span class="sev ${f.severity}">${f.severity}</span>${f.risk} — <code>${f.pattern}</code> at line ${f.line} <span style="color:var(--muted)">${f.cwe}</span></div>`;
    });
    html += '</div>';
  }

  // Bandit
  const bf = (layers.bandit || {}).findings || [];
  if (bf.length) {
    html += '<div class="findings-section"><div class="section-title">Bandit Security Scan</div>';
    bf.forEach(f => {
      html += `<div class="finding ${f.severity}"><span class="sev ${f.severity}">${f.severity}</span>${f.risk} — ${f.test_name} at line ${f.line} <span style="color:var(--muted)">${f.cwe}</span></div>`;
    });
    html += '</div>';
  }

  // AST
  const a = layers.ast_analysis || {};
  if (a.available) {
    const issues = a.issues || [];
    const sugs = a.suggestions || [];
    if (issues.length || sugs.length) {
      html += '<div class="findings-section"><div class="section-title">AST Analysis</div>';
      html += `<div class="finding LOW">Functions: ${a.functions} | Classes: ${a.classes} | Imports: ${a.imports} | Syntax: ${a.syntax_valid ? '✓' : '✗'}</div>`;
      issues.forEach(i => {
        html += `<div class="finding ${i.severity}"><span class="sev ${i.severity}">${i.severity}</span>${i.message}</div>`;
      });
      sugs.forEach(s => {
        html += `<div class="finding LOW">💡 ${s.type}: ${s.count} functions (${(s.functions||[]).join(', ')})</div>`;
      });
      html += '</div>';
    }
  }

  // Radon
  const r = layers.radon || {};
  if (r.available) {
    html += '<div class="findings-section"><div class="section-title">Complexity Analysis</div>';
    html += `<div class="finding LOW">Maintainability Index: ${r.maintainability_index || '?'} | Avg Complexity: ${r.avg_complexity} | Max: ${r.max_complexity} | Functions: ${r.functions_analyzed}</div>`;
    (r.complex_functions || []).forEach(f => {
      html += `<div class="finding MEDIUM"><span class="sev MEDIUM">COMPLEX</span>${f.name} — complexity ${f.complexity} (rank ${f.rank}) at line ${f.line}</div>`;
    });
    html += '</div>';
  }

  // LLM
  if (deep) {
    const llm = layers.eden_llm || {};
    html += '<div class="findings-section"><div class="section-title">Eden AI Review</div>';
    if (llm.available) {
      html += `<div class="llm-review">${llm.review}</div>`;
    } else {
      html += `<div class="finding MEDIUM">AI review unavailable: ${llm.reason || 'unknown'}</div>`;
    }
    html += '</div>';
  }

  el.innerHTML = html;
  el.style.display = 'block';
  el.scrollIntoView({behavior: 'smooth'});
}
</script>
</body>
</html>"""


@app.route("/")
def home():
    return Response(LANDING_HTML, mimetype="text/html")


# ═══════════════════════════════════════════════════════════
# Backwards-compatible /api/audit endpoint
# ═══════════════════════════════════════════════════════════
@app.route("/api/audit", methods=["POST"])
def audit_compat():
    """Legacy endpoint — redirects to new review."""
    data = request.json or {}
    code = data.get("code", "")
    lang = data.get("language", "python").lower()
    if not code:
        return jsonify({"error": "No code provided"}), 400

    patterns = layer1_patterns(code)
    grade, score = compute_grade(patterns, {}, {}, {})

    return jsonify({
        "status": "success",
        "report": {
            "grade": grade,
            "score": score,
            "vulnerabilities": patterns,
        }
    })


if __name__ == "__main__":
    print("SAGE v3.0 — 5-Layer Code Security Scanner")
    print(f"  Layers: pattern_match, ast, bandit, radon, eden_llm")
    print(f"  Model: {LLM_MODEL}")
    print(f"  Port: 8080")
    app.run(host="0.0.0.0", port=8080)
