/**
 * Security CVE issues page — dependency vulnerability scanner findings.
 *
 *   GET /:owner/:repo/security/vulnerabilities
 *
 * Shows all open issues that were auto-generated by the dependency CVE scanner
 * (identified by `[CVE]` prefix in title or the weekly digest marker in body).
 * Groups by severity (critical → high → medium → low) with count badges.
 * Shows an "All clear" panel when no security issues are open.
 *
 * This is a pure surfacing layer over the existing `issues` table — no new
 * schema changes required.
 */

import { Hono } from "hono";
import { and, desc, eq, ilike, or } from "drizzle-orm";
import { db } from "../db";
import { issues, repositories, users } from "../db/schema";
import { Layout } from "../views/layout";
import { RepoHeader, RepoNav } from "../views/components";
import { softAuth } from "../middleware/auth";
import type { AuthEnv } from "../middleware/auth";
import { __internal as scanInternal } from "../lib/dependency-scanner";

const securityRoutes = new Hono<AuthEnv>();
securityRoutes.use("*", softAuth);

/* ─────────────────────────────────────────────────────────────────────────
 * Scoped CSS — every class prefixed `.svp-` (Security Vulnerability Page).
 * Matches the gradient-hairline + radial-orb design language used by
 * code-scanning.tsx and admin-integrations.tsx.
 * ───────────────────────────────────────────────────────────────────── */
const styles = `
  .svp-wrap { max-width: 1680px; margin: 0 auto; padding: var(--space-5) var(--space-4); }

  .svp-hero {
    position: relative;
    margin-bottom: var(--space-5);
    padding: var(--space-5) var(--space-6);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 16px;
    overflow: hidden;
  }
  .svp-hero::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, transparent 0%, #d73a4a 30%, #f97316 70%, transparent 100%);
    opacity: 0.75;
    pointer-events: none;
  }
  .svp-hero-orb {
    position: absolute;
    inset: -30% -15% auto auto;
    width: 460px; height: 460px;
    background: radial-gradient(circle, rgba(215,58,74,0.18), rgba(249,115,22,0.08) 45%, transparent 70%);
    filter: blur(80px);
    opacity: 0.7;
    pointer-events: none;
    z-index: 0;
  }
  .svp-hero-inner { position: relative; z-index: 1; max-width: 760px; }
  .svp-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-transform: uppercase;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.18em;
    color: var(--text-muted);
    font-weight: 600;
    margin-bottom: 14px;
  }
  .svp-eyebrow-dot {
    width: 8px; height: 8px;
    border-radius: 9999px;
    background: linear-gradient(135deg, #d73a4a, #f97316);
    box-shadow: 0 0 0 3px rgba(215,58,74,0.18);
  }
  .svp-title {
    font-family: var(--font-display);
    font-size: clamp(24px, 3.5vw, 36px);
    font-weight: 800;
    letter-spacing: -0.025em;
    line-height: 1.05;
    margin: 0 0 var(--space-2);
    color: var(--text-strong);
  }
  .svp-title-grad {
    background-image: linear-gradient(135deg, #f87171 0%, #d73a4a 50%, #f97316 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
  }
  .svp-sub {
    font-size: 15px;
    color: var(--text-muted);
    margin: 0;
    line-height: 1.55;
    max-width: 640px;
  }

  /* Counts grid */
  .svp-counts {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-3);
    margin-bottom: var(--space-5);
  }
  @media (max-width: 680px) {
    .svp-counts { grid-template-columns: repeat(2, 1fr); }
  }
  .svp-count-card {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: var(--space-4);
    transition: border-color 120ms ease, transform 120ms ease;
  }
  .svp-count-card:hover { border-color: var(--border-strong, var(--border)); transform: translateY(-1px); }
  .svp-count-card.is-critical { border-color: rgba(239,68,68,0.4); }
  .svp-count-card.is-high { border-color: rgba(249,115,22,0.4); }
  .svp-count-card.is-medium { border-color: rgba(234,179,8,0.3); }
  .svp-count-card.is-low { border-color: rgba(99,102,241,0.25); }
  .svp-count-label {
    font-size: 10.5px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-weight: 700;
    margin-bottom: 6px;
  }
  .svp-count-value {
    font-family: var(--font-display);
    font-size: 36px;
    font-weight: 800;
    letter-spacing: -0.025em;
    line-height: 1;
    color: var(--text-strong);
    font-variant-numeric: tabular-nums;
  }
  .svp-count-card.is-critical .svp-count-value { color: #fca5a5; }
  .svp-count-card.is-high .svp-count-value { color: #fdba74; }
  .svp-count-card.is-medium .svp-count-value { color: #fde047; }
  .svp-count-card.is-low .svp-count-value { color: #a5b4fc; }
  .svp-count-hint { margin-top: 6px; font-size: 12px; color: var(--text-muted); }

  /* All-clear banner */
  .svp-clear {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: var(--space-4);
    padding: 16px 20px;
    border-radius: 14px;
    background: linear-gradient(135deg, rgba(52,211,153,0.10), rgba(54,197,214,0.06));
    border: 1px solid rgba(52,211,153,0.32);
    color: #bbf7d0;
  }
  .svp-clear-icon {
    flex: 0 0 auto;
    width: 38px; height: 38px;
    border-radius: 9999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #34d399 0%, #36c5d6 100%);
    color: #04231a;
    font-size: 18px;
    box-shadow: 0 0 0 4px rgba(52,211,153,0.16);
  }
  .svp-clear-text strong { display: block; color: #d1fae5; font-weight: 700; font-size: 15px; margin-bottom: 2px; }
  .svp-clear-text span { color: rgba(187,247,208,0.85); font-size: 13px; }

  /* Section heading */
  .svp-section-heading {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: var(--space-3);
  }
  .svp-section-title {
    font-family: var(--font-display);
    font-size: 17px;
    font-weight: 700;
    color: var(--text-strong);
    letter-spacing: -0.012em;
    margin: 0;
  }
  .svp-section-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border-radius: 9999px;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    background: var(--bg-subtle, rgba(255,255,255,0.06));
    border: 1px solid var(--border);
    color: var(--text-muted);
  }

  /* Issue list */
  .svp-issue-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
  }
  .svp-issue-card {
    position: relative;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--space-3) var(--space-4);
    transition: border-color 120ms ease, transform 120ms ease;
  }
  .svp-issue-card:hover { border-color: var(--border-strong, var(--border)); transform: translateY(-1px); }
  .svp-issue-card.sev-critical { border-left: 3px solid #ef4444; }
  .svp-issue-card.sev-high { border-left: 3px solid #f97316; }
  .svp-issue-card.sev-medium { border-left: 3px solid #eab308; }
  .svp-issue-card.sev-low { border-left: 3px solid #6366f1; }
  .svp-issue-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 14px;
    flex-wrap: wrap;
  }
  .svp-issue-main { min-width: 0; flex: 1; }
  .svp-issue-title {
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 700;
    color: var(--text-strong);
    letter-spacing: -0.005em;
    line-height: 1.35;
    margin: 0 0 4px;
    word-break: break-word;
  }
  .svp-issue-title a { color: inherit; text-decoration: none; }
  .svp-issue-title a:hover { text-decoration: underline; }
  .svp-issue-meta {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
    margin-top: 4px;
  }
  .svp-issue-num { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
  .svp-issue-date { font-size: 12px; color: var(--text-muted); }
  .svp-sev-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border-radius: 9999px;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    white-space: nowrap;
  }
  .svp-sev-pill.sev-critical { background: rgba(239,68,68,0.15); color: #fca5a5; border: 1px solid rgba(239,68,68,0.35); }
  .svp-sev-pill.sev-high { background: rgba(249,115,22,0.15); color: #fdba74; border: 1px solid rgba(249,115,22,0.35); }
  .svp-sev-pill.sev-medium { background: rgba(234,179,8,0.12); color: #fde047; border: 1px solid rgba(234,179,8,0.30); }
  .svp-sev-pill.sev-low { background: rgba(99,102,241,0.12); color: #a5b4fc; border: 1px solid rgba(99,102,241,0.28); }
  .svp-sev-pill.sev-digest { background: rgba(107,114,128,0.12); color: var(--text-muted); border: 1px solid var(--border); }

  /* Last scan info */
  .svp-scan-info {
    margin-top: var(--space-5);
    padding: 14px 18px;
    background: var(--bg-subtle, rgba(255,255,255,0.03));
    border: 1px solid var(--border);
    border-radius: 12px;
    font-size: 12.5px;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    gap: 8px;
  }

  /* How-it-works aside */
  .svp-how {
    margin-top: var(--space-4);
    padding: 14px 18px;
    background: rgba(140,109,255,0.06);
    border: 1px solid rgba(140,109,255,0.18);
    border-radius: 12px;
    font-size: 13px;
    color: var(--text-muted);
    line-height: 1.55;
  }
  .svp-how strong { color: var(--text-normal); }
`;

// ---------------------------------------------------------------------------
// Route
// ---------------------------------------------------------------------------

interface SecurityIssueRow {
  id: string;
  number: number;
  title: string;
  body: string | null;
  createdAt: Date;
  updatedAt: Date;
}

/** Infer severity from issue title (set by the scanner). */
function inferSeverityFromTitle(
  title: string
): "critical" | "high" | "medium" | "low" | "digest" {
  const upper = title.toUpperCase();
  if (upper.includes("CRITICAL")) return "critical";
  if (upper.includes("HIGH")) return "high";
  if (upper.includes("MEDIUM")) return "medium";
  if (upper.includes("LOW")) return "low";
  // Weekly digest issues
  if (upper.includes("DIGEST")) return "digest";
  return "medium";
}

function severityLabel(sev: ReturnType<typeof inferSeverityFromTitle>): string {
  switch (sev) {
    case "critical": return "CRITICAL";
    case "high": return "HIGH";
    case "medium": return "MEDIUM";
    case "low": return "LOW";
    case "digest": return "DIGEST";
  }
}

function fmtDate(d: Date): string {
  try {
    return d.toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric",
    });
  } catch {
    return "";
  }
}

securityRoutes.get("/:owner/:repo/security/vulnerabilities", async (c) => {
  const { owner: ownerName, repo: repoName } = c.req.param();
  const user = c.get("user");

  // Load repo — swallow DB errors so the route returns 404 gracefully
  // even when the DB is unavailable in tests or CI.
  let ownerRow: { id: string; username: string } | null = null;
  let repoRow: {
    id: string;
    ownerId: string;
    isPrivate: boolean;
    isArchived: boolean;
    isTemplate: boolean;
  } | null = null;
  try {
    const [o] = await db
      .select({ id: users.id, username: users.username })
      .from(users)
      .where(eq(users.username, ownerName))
      .limit(1);
    ownerRow = o || null;
  } catch {
    return c.notFound();
  }
  if (!ownerRow) return c.notFound();

  try {
    const [r] = await db
      .select({
        id: repositories.id,
        ownerId: repositories.ownerId,
        isPrivate: repositories.isPrivate,
        isArchived: repositories.isArchived,
        isTemplate: repositories.isTemplate,
      })
      .from(repositories)
      .where(
        and(
          eq(repositories.ownerId, ownerRow.id),
          eq(repositories.name, repoName)
        )
      )
      .limit(1);
    repoRow = r || null;
  } catch {
    return c.notFound();
  }
  if (!repoRow) return c.notFound();

  // Private repo — must be owner or collaborator (soft check: must be logged in as owner)
  if (repoRow.isPrivate && (!user || user.id !== ownerRow.id)) {
    return c.notFound();
  }

  // Fetch open security issues: those with [CVE] in title or "Dependency scan digest" in title
  let securityIssues: SecurityIssueRow[] = [];
  try {
    securityIssues = await db
      .select({
        id: issues.id,
        number: issues.number,
        title: issues.title,
        body: issues.body,
        createdAt: issues.createdAt,
        updatedAt: issues.updatedAt,
      })
      .from(issues)
      .where(
        and(
          eq(issues.repositoryId, repoRow.id),
          eq(issues.state, "open"),
          or(
            ilike(issues.title, "%[CVE]%"),
            ilike(issues.title, "%Dependency scan digest%")
          )
        )
      )
      .orderBy(desc(issues.createdAt))
      .limit(200);
  } catch {
    securityIssues = [];
  }

  // Compute counts per severity
  const counts = { critical: 0, high: 0, medium: 0, low: 0, digest: 0 };
  for (const issue of securityIssues) {
    const sev = inferSeverityFromTitle(issue.title);
    counts[sev]++;
  }

  // Find the most recent scan timestamp (most recent security issue created_at)
  const lastScanAt =
    securityIssues.length > 0
      ? securityIssues.reduce((latest, i) =>
          i.createdAt > latest ? i.createdAt : latest,
          securityIssues[0].createdAt
        )
      : null;

  // Group by severity for display
  const groups: Array<{
    sev: ReturnType<typeof inferSeverityFromTitle>;
    label: string;
    issues: SecurityIssueRow[];
  }> = [
    { sev: "critical", label: "Critical", issues: [] },
    { sev: "high", label: "High", issues: [] },
    { sev: "medium", label: "Medium", issues: [] },
    { sev: "low", label: "Low", issues: [] },
    { sev: "digest", label: "Digest", issues: [] },
  ];

  for (const issue of securityIssues) {
    const sev = inferSeverityFromTitle(issue.title);
    const group = groups.find((g) => g.sev === sev);
    if (group) group.issues.push(issue);
  }

  const hasFindings = securityIssues.length > 0;

  return c.html(
    <Layout title={`Security vulnerabilities — ${ownerName}/${repoName}`} user={user}>
      <style dangerouslySetInnerHTML={{ __html: styles }} />
      <div class="svp-wrap">
        {/* Repo header + nav */}
        <RepoHeader
          owner={ownerName}
          repo={repoName}
          currentUser={user?.username}
          archived={repoRow.isArchived}
          isTemplate={repoRow.isTemplate}
        />
        <RepoNav owner={ownerName} repo={repoName} active="security" />

        {/* Hero */}
        <div class="svp-hero">
          <div class="svp-hero-orb" />
          <div class="svp-hero-inner">
            <div class="svp-eyebrow">
              <span class="svp-eyebrow-dot" />
              Dependency Scanner
            </div>
            <h1 class="svp-title">
              <span class="svp-title-grad">CVE</span> Vulnerabilities
            </h1>
            <p class="svp-sub">
              Auto-detected dependency vulnerabilities from push-time scans via the{" "}
              <a href="https://osv.dev" target="_blank" rel="noopener noreferrer" style="color:inherit">
                OSV database
              </a>
              . Critical and high findings open issues immediately. Medium/low findings are batched into a weekly digest.
            </p>
          </div>
        </div>

        {/* Severity count grid */}
        <div class="svp-counts">
          <div class={`svp-count-card${counts.critical > 0 ? " is-critical" : ""}`}>
            <div class="svp-count-label">Critical</div>
            <div class="svp-count-value">{counts.critical}</div>
            <div class="svp-count-hint">open issues</div>
          </div>
          <div class={`svp-count-card${counts.high > 0 ? " is-high" : ""}`}>
            <div class="svp-count-label">High</div>
            <div class="svp-count-value">{counts.high}</div>
            <div class="svp-count-hint">open issues</div>
          </div>
          <div class={`svp-count-card${counts.medium > 0 ? " is-medium" : ""}`}>
            <div class="svp-count-label">Medium</div>
            <div class="svp-count-value">{counts.medium}</div>
            <div class="svp-count-hint">open issues</div>
          </div>
          <div class={`svp-count-card${counts.low > 0 ? " is-low" : ""}`}>
            <div class="svp-count-label">Low</div>
            <div class="svp-count-value">{counts.low}</div>
            <div class="svp-count-hint">open issues</div>
          </div>
        </div>

        {/* All clear or findings */}
        {!hasFindings ? (
          <div class="svp-clear">
            <span class="svp-clear-icon">✓</span>
            <div class="svp-clear-text">
              <strong>All clear</strong>
              <span>
                No open dependency vulnerability issues detected.{" "}
                {lastScanAt
                  ? `Last scanned ${fmtDate(lastScanAt)}.`
                  : "Enable DEPENDENCY_SCAN_ENABLED=1 to start scanning pushes automatically."}
              </span>
            </div>
          </div>
        ) : (
          <>
            {groups
              .filter((g) => g.issues.length > 0)
              .map((group) => (
                <div>
                  <div class="svp-section-heading">
                    <h2 class="svp-section-title">{group.label} severity</h2>
                    <span class="svp-section-badge">{group.issues.length}</span>
                  </div>
                  <div class="svp-issue-list">
                    {group.issues.map((issue) => (
                      <div class={`svp-issue-card sev-${group.sev}`}>
                        <div class="svp-issue-row">
                          <div class="svp-issue-main">
                            <p class="svp-issue-title">
                              <a href={`/${ownerName}/${repoName}/issues/${issue.number}`}>
                                {issue.title}
                              </a>
                            </p>
                            <div class="svp-issue-meta">
                              <span class="svp-issue-num">#{issue.number}</span>
                              <span class="svp-issue-date">
                                Opened {fmtDate(issue.createdAt)}
                              </span>
                            </div>
                          </div>
                          <span
                            class={`svp-sev-pill sev-${group.sev}`}
                          >
                            {severityLabel(group.sev)}
                          </span>
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              ))}
          </>
        )}

        {/* Last scan info */}
        {lastScanAt && (
          <div class="svp-scan-info">
            <span>🕐</span>
            <span>
              Last scan: <strong>{fmtDate(lastScanAt)}</strong> — scans run automatically on every push that touches{" "}
              <code>package.json</code>, <code>requirements.txt</code>,{" "}
              <code>Cargo.toml</code>, <code>go.mod</code>, or <code>Gemfile</code>.
            </span>
          </div>
        )}

        {/* How it works */}
        <div class="svp-how">
          <strong>How it works:</strong> GlueCron scans your dependency files on
          every push using the{" "}
          <a href="https://osv.dev" target="_blank" rel="noopener noreferrer">
            OSV (Open Source Vulnerabilities)
          </a>{" "}
          database. Critical and high severity CVEs open individual issues
          immediately. Medium and low findings are batched into a weekly digest
          issue. Requires <code>DEPENDENCY_SCAN_ENABLED=1</code> on your
          deployment.
          {" "}
          <a href={`/${ownerName}/${repoName}/security`}>
            View gate scan results →
          </a>
        </div>
      </div>
    </Layout>
  );
});

export default securityRoutes;
