// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2026 ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG, Cologne
// SPDX-FileCopyrightText: Author: Pierre Gronau <Pierre.Gronau@ndaal.eu>

//! The "Info" menu pages — About, System info, Privacy, Security, License,
//! Imprint and Changelog — reached from the right-corner dropdown, mirroring
//! the ndaal OSCAL/CSAF viewers.

use std::fmt::Write as _;

use http::{Response, StatusCode};

use crate::app_state::AppState;
use crate::html::{self, escape, Nav};
use crate::router::{html_response, Body};

/// The application version, baked in at compile time.
const VERSION: &str = env!("CARGO_PKG_VERSION");

/// Render a page body inside the shell.
fn render(title: &str, body: &str) -> Response<Body> {
    html_response(StatusCode::OK, html::page(title, Nav::None, body))
}

/// `GET /about` — capabilities and provenance.
pub fn about(
    state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let body = format!(
        "<h1 class=\"h3 mb-4\"><i class=\"bi bi-info-square me-2\"></i>About SARIF Viewer</h1>\
         <div class=\"card shadow-sm mb-4\"><div class=\"card-body\">\
         <p>An offline, single-binary <strong>viewer, validator and multi-format exporter</strong> \
         for SARIF 2.1.0 and SARIF 2.2 (draft) documents. Version <span class=\"mono\">{version}</span>.</p>\
         <h2 class=\"h5 mt-4\"><i class=\"bi bi-check2-circle me-2\"></i>Validation</h2>\
         <p>Every imported and exported document is validated against the embedded OASIS SARIF \
         schemas — the 2.1.0 errata schema and the 2.2 working draft — plus a structural-conformity \
         floor. Invalid documents are rejected on import and refused on export.</p>\
         <h2 class=\"h5 mt-4\"><i class=\"bi bi-box-arrow-up me-2\"></i>Export formats</h2>\
         <ul><li><strong>SARIF (JSON)</strong> — the validated document</li>\
         <li><strong>Markdown</strong> and <strong>ODT</strong> — via the pure-Rust <span class=\"mono\">lo_writer</span> document model</li>\
         <li><strong>PDF</strong> — rendered with an embedded Roboto font (full Unicode)</li></ul>\
         <p>Each exported file is accompanied by optional checksum sidecars \
         (SHA-256, SHA-512, SHA3-512, BLAKE3-512, SHAKE256-512).</p>\
         <h2 class=\"h5 mt-4\"><i class=\"bi bi-diagram-3 me-2\"></i>Provenance</h2>\
         <p>Reuses the ndaal CSAF-CRUD validation/import/export and TLS+QUIC listener, and the \
         BSI-Grundschutz OSCAL viewer's Bootstrap web layer and multi-format export engine.</p>\
         </div></div>\
         <div class=\"card shadow-sm\"><div class=\"card-body text-body-secondary small\">\
         Serving on <span class=\"mono\">{listen}</span> · {logs} log(s) loaded · uptime {uptime}s\
         </div></div>",
        version = escape(VERSION),
        listen = escape(state.listen()),
        logs = state.store().len(),
        uptime = state.uptime_secs(),
    );
    render("About", &body)
}

/// One `<tr>` for a key/value info table.
fn row(key: &str, value: &str) -> String {
    format!(
        "<tr><th scope=\"row\" class=\"text-body-secondary\" style=\"width:14rem\">{}</th><td class=\"mono\">{}</td></tr>",
        escape(key),
        escape(value)
    )
}

/// `GET /system-info` — runtime and platform facts (std-only, multiplatform).
pub fn system_info(
    state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let mut rows = String::new();
    rows.push_str(&row("Application", &format!("sarif-viewer {VERSION}")));
    rows.push_str(&row("Operating system", std::env::consts::OS));
    rows.push_str(&row("Architecture", std::env::consts::ARCH));
    rows.push_str(&row("OS family", std::env::consts::FAMILY));
    rows.push_str(&row("Listen address (HTTPS/TCP)", state.listen()));
    rows.push_str(&row(
        "Protocols",
        "HTTP/1.1 + HTTP/2 (TLS 1.3) over TCP · HTTP/3 over QUIC/UDP",
    ));
    rows.push_str(&row("Export directory", state.export_dir()));
    rows.push_str(&row("Logs loaded", &state.store().len().to_string()));
    rows.push_str(&row("Uptime (seconds)", &state.uptime_secs().to_string()));

    let body = format!(
        "<h1 class=\"h3 mb-4\"><i class=\"bi bi-cpu me-2\"></i>System information</h1>\
         <div class=\"card shadow-sm\"><div class=\"card-body\">\
         <div class=\"table-responsive\"><table class=\"table table-sm align-middle mb-0\">\
         <tbody>{rows}</tbody></table></div></div></div>"
    );
    render("System info", &body)
}

/// `GET /privacy` — the privacy statement.
pub fn privacy(
    _state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let body = "<h1 class=\"h3 mb-4\"><i class=\"bi bi-eye-slash me-2\"></i>Privacy</h1>\
         <div class=\"card shadow-sm\"><div class=\"card-body\">\
         <p>SARIF Viewer is an <strong>offline, single-user desktop tool</strong>. It makes no \
         outbound network connections, contains no telemetry, analytics or trackers, and loads no \
         third-party resources — Bootstrap, the icon font and the fonts are all served from the \
         binary itself.</p>\
         <h2 class=\"h5 mt-4\">What is processed</h2>\
         <ul>\
         <li>SARIF documents you import are held <strong>in memory only</strong> and are lost when \
         the process exits. Nothing is written unless you explicitly use the Export menu.</li>\
         <li>Exported files (and their checksum sidecars) are written only to the export directory \
         you configure.</li>\
         <li>The server binds <span class=\"mono\">127.0.0.1</span> (loopback); no remote client can reach it.</li>\
         </ul>\
         <h2 class=\"h5 mt-4\">Browser storage</h2>\
         <p>The only browser storage used is a single <span class=\"mono\">localStorage</span> key \
         (<span class=\"mono\">sarif-theme</span>) remembering your light/dark choice. No cookies are set.</p>\
         </div></div>";
    render("Privacy", body)
}

/// `GET /security` — the security posture.
pub fn security(
    _state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let body = "<h1 class=\"h3 mb-4\"><i class=\"bi bi-shield-check me-2\"></i>Security</h1>\
         <div class=\"card shadow-sm mb-4\"><div class=\"card-body\">\
         <p>Our software is developed in Rust, a programming language designed for memory safety \
         and type safety without sacrificing system-level performance. By leveraging Rust&rsquo;s \
         ownership model, strict type system, and compile-time checks, we effectively prevent \
         common classes of vulnerabilities such as memory corruption, buffer overflows, and \
         use-after-free errors.</p>\
         <p>Unsafe code is forbidden across the crate (<span class=\"mono\">#![forbid(unsafe_code)]</span>), \
         and the linting policy denies panicking shortcuts (<span class=\"mono\">unwrap</span>, \
         <span class=\"mono\">expect</span>, <span class=\"mono\">panic</span>), unchecked arithmetic and \
         unchecked indexing in production code.</p>\
         <p>The viewer serves HTTPS only: a TLS&nbsp;1.3 listener built on rustls (aws-lc-rs provider \
         with <span class=\"mono\">prefer-post-quantum</span>, so the X25519MLKEM768 post-quantum hybrid \
         key-exchange group is offered first), with a self-signed certificate regenerated at every \
         start-up. It binds to localhost by default, serves only the in-memory SARIF logs the operator \
         has loaded together with the embedded static assets, and sets defence-in-depth response headers \
         on every response: HTTP Strict-Transport-Security, X-Frame-Options DENY, a restrictive \
         Content-Security-Policy, X-Content-Type-Options nosniff, referrer minimisation, a \
         Permissions-Policy deny list and cross-origin isolation headers. HTTP/3 is additionally offered \
         over QUIC on the same provider.</p>\
         <p>Filesystem writes happen only on explicit request (the Export menu or the \
         <span class=\"mono\">export</span> subcommand) and are performed through capability-based \
         cap-std directory handles with create-new semantics, preventing path traversal and \
         time-of-check/time-of-use attacks.</p>\
         <p>Static code analysis is enforced with Clippy (pedantic), cargo-audit and cargo-deny. \
         We explicitly guarantee that our software contains no built-in backdoors or hidden \
         functionalities. The code is developed transparently and adheres to the principles of \
         trustworthy and verifiable software engineering.</p>\
         <p class=\"mb-0\">Our processes and software design therefore fulfil state-of-the-art IT \
         security and data protection requirements, supporting the principles of privacy by design \
         and privacy by default. The solution meets the criteria for the TeleTrusT trust marks \
         &ldquo;IT Security made in Germany&rdquo; and &ldquo;IT Security made in Europe&rdquo;.</p>\
         </div></div>\
         <div class=\"card shadow-sm\"><div class=\"card-body\">\
         <h2 class=\"h5 card-title\">Technical controls</h2>\
         <ul>\
         <li><strong>Transport.</strong> TLS 1.3 only — HTTP/1.1 + HTTP/2 over TCP and HTTP/3 over \
         QUIC/UDP — on the rustls aws-lc-rs provider with <span class=\"mono\">prefer-post-quantum</span> \
         (X25519MLKEM768 offered first); self-signed localhost certificate minted at start-up.</li>\
         <li><strong>Schema validation gate.</strong> Every import and export is validated against \
         the embedded SARIF 2.1.0 and 2.2 schemas; invalid documents are rejected on import and \
         refused on export.</li>\
         <li><strong>Response hardening.</strong> Strict security headers on every response: CSP \
         <span class=\"mono\">script-src 'self'</span> (no inline scripts), HSTS, \
         <span class=\"mono\">X-Frame-Options: DENY</span>, <span class=\"mono\">nosniff</span>, \
         <span class=\"mono\">Referrer-Policy: no-referrer</span>, COOP/COEP/CORP.</li>\
         <li><strong>Path security.</strong> Exports are written through a capability-scoped \
         (<span class=\"mono\">cap-std</span> 4.x, <span class=\"mono\">openat2(RESOLVE_BENEATH)</span>) \
         directory handle from a single audited boundary; output filenames are slugified and \
         re-checked as safe single segments. A path-traversal regression corpus and a dedicated fuzz \
         target guard the lexical primitive — per the ndaal <span class=\"mono\">rust-path-security</span> \
         skill. Per-file checksum sidecars (SHA-256/512, SHA3-512, BLAKE3-512, SHAKE256-512) support \
         integrity verification.</li>\
         <li><strong>Connection limits.</strong> Bounded concurrency, a TLS-handshake timeout and a \
         slowloris header-read timeout.</li>\
         </ul>\
         <p class=\"mb-0\">Report a vulnerability to \
         <a href=\"mailto:security@ndaal.eu\">security@ndaal.eu</a>.</p>\
         </div></div>";
    render("Security", body)
}

/// `GET /license` — application and bundled-asset licenses.
pub fn license(
    _state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let body = "<h1 class=\"h3 mb-4\"><i class=\"bi bi-file-earmark-text me-2\"></i>License information</h1>\
         <div class=\"card shadow-sm mb-4\"><div class=\"card-header bg-transparent\">\
         <i class=\"bi bi-code-slash me-2\"></i>Application license — Apache-2.0</div>\
         <div class=\"card-body\"><p class=\"mb-0\">\
         © 2026 ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH &amp; Co KG, Cologne. \
         Licensed under the Apache License, Version 2.0. \
         See <a href=\"https://www.apache.org/licenses/LICENSE-2.0\" rel=\"noopener noreferrer\" target=\"_blank\">apache.org/licenses/LICENSE-2.0</a>.\
         </p></div></div>\
         <div class=\"card shadow-sm mb-4\"><div class=\"card-header bg-transparent\">\
         <i class=\"bi bi-file-earmark-code me-2\"></i>Embedded SARIF schemas</div>\
         <div class=\"card-body\"><p class=\"mb-0\">\
         The bundled SARIF 2.1.0 and SARIF 2.2 (draft) JSON Schemas are vendored from the OASIS \
         <span class=\"mono\">sarif-spec</span> repository, © OASIS Open.</p></div></div>\
         <div class=\"card shadow-sm\"><div class=\"card-header bg-transparent\">\
         <i class=\"bi bi-box-seam me-2\"></i>Embedded third-party assets</div>\
         <div class=\"card-body\"><ul class=\"mb-0\">\
         <li>Bootstrap 5.3 &amp; Bootstrap Icons — MIT, © The Bootstrap Authors</li>\
         <li>Roboto — Apache-2.0, © Google LLC</li>\
         <li><span class=\"mono\">lo_writer</span> / <span class=\"mono\">ttf-parser</span> / \
         <span class=\"mono\">jsonschema</span> and other Rust crates — see their respective licenses</li>\
         </ul></div></div>";
    render("License", body)
}

/// `(heading, paragraphs)` for the German legal notice (§ 5 TMG / § 10 MStV),
/// reproduced from the ndaal OSCAL viewer.
type ImprintSection = (&'static str, &'static [&'static str]);

/// The imprint sections — the ndaal company legal details.
const IMPRINT_SECTIONS: &[ImprintSection] = &[
    (
        "Impressum / Herausgeber",
        &[
            "Verantwortlicher im Sinne des § 10 Absatz 3 Mediendienste-Staatsvertrag",
            "ndaal GmbH & Co. KG",
            "Geschäftsführung: Carsten Dingendahl",
        ],
    ),
    (
        "Anschrift",
        &[
            "ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG",
            "Christophstr. 15-17",
            "50670 Köln",
            "Deutschland",
            "Tel.: +49.221.65 086 200",
            "E-Mail: info@ndaal.eu",
            "Website: www.ndaal.eu",
        ],
    ),
    (
        "Firmensitz / Registergericht",
        &[
            "Köln / Amtsgericht Köln, HRA 35474",
            "Umsatzsteuer-Identifikationsnummer: DE329537036",
        ],
    ),
    (
        "Komplementärin",
        &[
            "XSTeam Beteiligungsgesellschaft mbH",
            "Geschäftsführung: Carsten Dingendahl",
            "Firmensitz / Registergericht: Köln / Amtsgericht Köln, HRB 105499",
            "E-Mail: info@xsteam.eu",
        ],
    ),
    (
        "Haftung für Inhalte",
        &[
            "Als Diensteanbieter sind wir gemäß § 7 Abs. 1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.",
        ],
    ),
    (
        "Urheberrecht",
        &[
            "Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Beiträge Dritter sind als solche gekennzeichnet.",
        ],
    ),
];

/// `GET /imprint` — the German legal notice.
pub fn imprint(
    _state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let mut sections = String::new();
    for (index, (heading, paragraphs)) in IMPRINT_SECTIONS.iter().enumerate() {
        let margin = if index == 0 { "" } else { " mt-4" };
        let _ = write!(sections, "<h2 class=\"h5{margin}\">{}</h2>", escape(heading));
        for paragraph in *paragraphs {
            let _ = write!(sections, "<p class=\"mb-1\">{}</p>", escape(paragraph));
        }
    }
    let body = format!(
        "<h1 class=\"h3 mb-4\"><i class=\"bi bi-building me-2\"></i>Impressum</h1>\
         <div class=\"card shadow-sm\"><div class=\"card-body\">{sections}</div></div>"
    );
    render("Imprint", &body)
}

/// `GET /changelog` — the release history.
pub fn changelog(
    _state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let body = format!(
        "<h1 class=\"h3 mb-4\"><i class=\"bi bi-card-list me-2\"></i>Changelog</h1>\
         <div class=\"card shadow-sm\"><div class=\"card-body\">\
         <h2 class=\"h5\">{version} — initial release</h2>\
         <ul class=\"mb-0\">\
         <li>SARIF 2.1.0 + 2.2 (draft) viewer, validator and exporter — single binary.</li>\
         <li>Schema-validation gate enforced on every import and export (dual 2.1.0 + 2.2).</li>\
         <li>Multi-format export: SARIF (JSON), Markdown, ODT and PDF, with five checksum sidecars.</li>\
         <li>Bootstrap web UI with a light/dark theme switcher; CLI <span class=\"mono\">validate</span> and <span class=\"mono\">export</span> subcommands.</li>\
         <li>HTTPS (TLS 1.3, HTTP/1.1 + HTTP/2) plus HTTP/3 over QUIC.</li>\
         </ul></div></div>",
        version = escape(VERSION),
    );
    render("Changelog", &body)
}
