Skip to content
use std::sync::Arc;

use actix_web::{
    HttpRequest, HttpResponse, Responder, cookie::Cookie, delete, get, post,
    web::{self, Data, Form, Path, Query},
};
use log::{error, info};
use maud::{DOCTYPE, Markup, PreEscaped, html};
use serde::Deserialize;

use corium::{
    auth::{HurlTest, Session, Ticket, User, ADMIN_API_KEY_COOKIE, SESSION_COOKIE},
    code::{Code, CodeVersion, DEFAULT_KOTLIN_CODE, Lang, TestVersion},
    compiler::Compiler,
    config::Server,
    runner::Runner,
    store::DB,
};

pub fn configure_routes(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("")
            .service(index)
            .service(features_page)
            .service(login_page)
            .service(login_handler)
            .service(signup_ticket_page)
            .service(signup_ticket_handler)
            .service(signup_page)
            .service(signup_handler)
            .service(logout_handler)
            .service(dashboard)
            .service(list_endpoints)
            .service(create_endpoint_form)
            .service(edit_endpoint_form)
            .service(create_endpoint)
            .service(update_endpoint)
            .service(delete_endpoint)
            .service(test_endpoint)
            .service(save_code_version)
            .service(list_code_versions)
            .service(get_code_version)
            .service(save_test_version)
            .service(list_test_versions)
            .service(get_test_version)
            .service(settings_page)
            .service(update_password)
            .service(admin_tickets_page)
            .service(create_ticket_handler)
            .service(delete_ticket_handler)
            .service(admin_login_page)
            .service(admin_login_handler)
            .service(admin_logout_handler)
            .service(serve_wasm)
            .service(run_endpoint)
            .service(list_endpoints_api)
            .service(health)
            .service(up)
    );
}

async fn get_user_id_from_request(req: &HttpRequest, db: &DB) -> Option<String> {
    let cookie = req.cookie(SESSION_COOKIE)?;
    let token = cookie.value().to_string();
    let session = db.get_session(&token).await?;
    if session.is_expired() {
        let _ = db.delete_session(&token).await;
        return None;
    }
    Some(session.user_id)
}

async fn get_user_from_request(req: &HttpRequest, db: &DB) -> Option<User> {
    let user_id = get_user_id_from_request(req, db).await?;
    db.get_user_by_id(&user_id).await
}

fn validate_admin_api_key(req: &HttpRequest, config: &Server) -> bool {
    if let Some(cookie) = req.cookie(ADMIN_API_KEY_COOKIE) {
        return cookie.value() == config.admin_api_key();
    }
    if let Some(header) = req.headers().get("X-Admin-API-Key") {
        if let Ok(val) = header.to_str() {
            return val == config.admin_api_key();
        }
    }
    false
}

fn html_escape(input: &str) -> String {
    input
        .replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
}

fn format_timestamp(ts: u64) -> String {
    let secs = ts as i64;
    let days_since_epoch = secs / 86400;
    let time_secs = secs % 86400;
    let hours = time_secs / 3600;
    let minutes = (time_secs % 3600) / 60;
    let year = 1970;
    let month = 1;
    let day = 1 + days_since_epoch;
    format!("{}-{:02}-{:02} {:02}:{:02}", year, month, day, hours, minutes)
}

fn set_session_cookie(token: &str, max_age: u64) -> Cookie<'static> {
    Cookie::build(SESSION_COOKIE, token.to_string())
        .path("/")
        .http_only(true)
        .max_age(actix_web::cookie::time::Duration::seconds(max_age as i64))
        .same_site(actix_web::cookie::SameSite::Lax)
        .finish()
}

fn clear_session_cookie() -> Cookie<'static> {
    Cookie::build(SESSION_COOKIE, "")
        .path("/")
        .http_only(true)
        .max_age(actix_web::cookie::time::Duration::seconds(0))
        .same_site(actix_web::cookie::SameSite::Lax)
        .finish()
}

fn set_admin_cookie(key: &str) -> Cookie<'static> {
    Cookie::build(ADMIN_API_KEY_COOKIE, key.to_string())
        .path("/")
        .http_only(true)
        .max_age(actix_web::cookie::time::Duration::days(1))
        .same_site(actix_web::cookie::SameSite::Lax)
        .finish()
}

fn clear_admin_cookie() -> Cookie<'static> {
    Cookie::build(ADMIN_API_KEY_COOKIE, "")
        .path("/")
        .http_only(true)
        .max_age(actix_web::cookie::time::Duration::seconds(0))
        .same_site(actix_web::cookie::SameSite::Lax)
        .finish()
}

fn layout(title: &str, content: Markup, user: Option<&User>) -> Markup {
    let nav_content = if let Some(_u) = user {
        html! {
            div class="flex justify-between items-center w-100" {
                a href="/" class="link orange f4 fw6" { "Corium" }
                div class="flex gap3 items-center" {
                    a href="/dashboard" class="link near-white f6" { "Dashboard" }
                    a href="/endpoints" class="link near-white f6" { "Endpoints" }
                    a href="/settings" class="link near-white f6" { "Settings" }
                    a href="/logout" class="link hot-pink f6 ml2" { "Logout" }
                }
            }
        }
    } else {
        html! {
            div class="flex justify-between items-center w-100" {
                a href="/" class="link orange f4 fw6" { "Corium" }
                div class="flex gap3" {
                    a href="/login" class="link near-white f6" { "Login" }
                    a href="/signup" class="link near-white f6" { "Sign Up" }
                }
            }
        }
    };

    html! {
        (DOCTYPE)
        html lang="en" {
            head {
                meta charset="utf-8" {}
                meta name="viewport" content="width=device-width, initial-scale=1" {}
                title { (title) }
                link rel="stylesheet" href="/assets/t.css" {}
                script src="/assets/h.js" {}
                script src="/assets/hx-response-targets.js" {}
                style {
                    (PreEscaped(r#"
                        * { font-family: 'Courier New', Courier, monospace; }
                        .min-h5 { min-height: 16rem; }
                        .gap2 { gap: 0.5rem; }
                        .gap3 { gap: 1rem; }
                        .code-editor {
                            font-family: 'Courier New', Courier, monospace;
                            font-size: 14px;
                            line-height: 1.5;
                            tab-size: 4;
                            white-space: pre;
                            overflow-wrap: normal;
                            overflow-x: auto;
                            background: #0a0a0a;
                            color: #ff6300;
                        }
                        .output-panel {
                            background: #0d0d0d;
                        }
                        input, select, textarea {
                            background: #0a0a0a;
                            color: #ff6300;
                            border-color: #333;
                        }
                        input:focus, select:focus, textarea:focus {
                            outline: none;
                            border-color: #ff6300 !important;
                        }
                        input::placeholder, textarea::placeholder {
                            color: #555 !important;
                        }
                        table { border-collapse: collapse; }
                        thead tr { background: #1a1a1a; }
                        trix-toolbar { margin-bottom: 0.5rem; }
                    "#))
                }
            }
            body class="monospace bg-near-black near-white" {
                nav class="pa3" {
                    (nav_content)
                }
                main {
                    (content)
                }
            }
        }
    }
}

fn landing_layout(title: &str, content: Markup) -> Markup {
    html! {
        (DOCTYPE)
        html lang="en" {
            head {
                meta charset="utf-8" {}
                meta name="viewport" content="width=device-width, initial-scale=1" {}
                title { (title) }
                link rel="stylesheet" href="/assets/t.css" {}
                style {
                    (PreEscaped(r#"
                        * { font-family: 'Courier New', Courier, monospace; }
                        .gap2 { gap: 0.5rem; }
                        .gap3 { gap: 1rem; }
                        input, select, textarea {
                            background: #0a0a0a;
                            color: #ff6300;
                            border-color: #333;
                        }
                        input:focus, select:focus, textarea:focus {
                            outline: none;
                            border-color: #ff6300 !important;
                        }
                        input::placeholder, textarea::placeholder {
                            color: #555 !important;
                        }
                    "#))
                }
            }
            body class="monospace bg-near-black near-white" {
                nav class="pa3" {
                    div class="flex justify-between items-center mw8 center" {
                        a href="/" class="link orange f4 fw6" { "Corium" }
                        div class="flex gap3" {
                            a href="/login" class="link light-silver f6" { "Login" }
                            a href="/signup" class="f6 link br2 ph2 pv1 dib orange ba b--orange" { "Sign Up" }
                        }
                    }
                }
                main {
                    (content)
                }
                footer class="pa4 mt5" {
                    div class="mw8 center tc" {
                        p class="f6 light-silver" { "Corium" }
                    }
                }
            }
        }
    }
}

// --- Landing Page ---

#[get("/")]
async fn index(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let user = get_user_from_request(&req, &db).await;
    if user.is_some() {
        return HttpResponse::Found()
            .append_header(("Location", "/dashboard"))
            .finish();
    }
    let content = html! {
        section class="pt5 pb5 ph3" {
            div class="mw7 center" {
                h1 class="f-subheadline-l f1 fw7 mb3 orange lh-solid" { "Corium" }
                p class="f4 fw3 light-silver mb4 lh-copy" {
                    "Deploy WASM endpoints in seconds."
                    br {}
                    "Write in Kotlin, compile to WebAssembly, run anywhere."
                }
                div class="flex gap3" {
                    a href="/signup" class="f5 link br2 ph3 pv2 dib orange ba b--orange" { "Get Started" }
                    a href="/features" class="f5 link br2 ph3 pv2 dib light-silver" { "Learn more" }
                }
            }
        }

        section class="pb5 ph3" {
            div class="mw7 center" {
                p class="f3 fw6 mb3 near-white" { "Ready to deploy?" }
                p class="f5 light-silver mb4 lh-copy" {
                    "Sign up with an invite code and start deploying WASM endpoints."
                }
                a href="/signup" class="f5 link br2 ph3 pv2 dib orange ba b--orange" { "Sign Up" }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(landing_layout("Corium - Deploy WASM Endpoints", content).into_string())
}

// --- Features Page ---

#[get("/features")]
async fn features_page() -> impl Responder {
    let content = html! {
        section class="pt2 pb1 ph3" {
            div class="mw7 center" {
                h1 class="f3 fw7 mb1 orange lh-solid" { "What is Corium?" }
                p class="f6 fw3 light-silver mb2 lh-copy" {
                    "Corium lets you write code, compile it to WebAssembly, and deploy it as an HTTP endpoint — all in seconds."
                }
            }
        }

        section class="pb2 ph3" {
            div class="mw7 center" {
                h2 class="f4 fw6 mb1 near-white" { "Write in Kotlin, Deploy as WASM" }
                p class="f6 light-silver lh-copy" {
                    "Write endpoint logic in Kotlin. Corium compiles it to WebAssembly — no containers, no cold starts, just your code ready to serve."
                }
            }
        }

        section class="pb2 ph3" {
            div class="mw7 center" {
                h2 class="f4 fw6 mb1 near-white" { "Instant Endpoints" }
                p class="f6 light-silver lh-copy" {
                    "Every deployment becomes a live HTTP endpoint. Define your path, write your logic, hit deploy — available immediately at a predictable URL."
                }
            }
        }

        section class="pb2 ph3" {
            div class="mw7 center" {
                h2 class="f4 fw6 mb1 near-white" { "Built-in Testing" }
                p class="f6 light-silver lh-copy" {
                    "Test endpoints right from the editor. Define request parameters, hit run, see the JSON response instantly."
                }
            }
        }

        section class="pb2 ph3" {
            div class="mw7 center" {
                h2 class="f4 fw6 mb1 near-white" { "Simple Auth & Access Control" }
                p class="f6 light-silver lh-copy" {
                    "Sign up with an invite code, create endpoints, manage everything from a clean dashboard. Ticket-based invitations keep the platform focused."
                }
            }
        }

        section class="pb2 ph3" {
            div class="mw7 center" {
                h2 class="f4 fw6 mb1 near-white" { "Lightweight & Self-Hosted" }
                p class="f6 light-silver lh-copy" {
                    "A single binary backed by SQLite. No Kubernetes, no message queues, no microservice sprawl. Deploy it anywhere."
                }
            }
        }

        section class="pb2 ph3" {
            div class="mw7 center tc" {
                a href="/signup" class="f5 link br2 ph3 pv2 dib orange ba b--orange" { "Create an Account" }
            }
        }
    };
    let full_page = html! {
        (DOCTYPE)
        html lang="en" {
            head {
                meta charset="utf-8" {}
                meta name="viewport" content="width=device-width, initial-scale=1" {}
                title { "Features - Corium" }
                link rel="stylesheet" href="/assets/t.css" {}
                style {
                    (PreEscaped(r#"
                        * { font-family: 'Courier New', Courier, monospace; }
                        .gap2 { gap: 0.5rem; }
                        .gap3 { gap: 1rem; }
                        input, select, textarea { background: #0a0a0a; color: #ff6300; border-color: #333; }
                        input:focus, select:focus, textarea:focus { outline: none; border-color: #ff6300 !important; }
                        input::placeholder, textarea::placeholder { color: #555 !important; }
                        html, body { height: 100%; overflow: hidden; }
                    "#))
                }
            }
            body class="monospace bg-near-black near-white" {
                nav class="pa3" {
                    div class="flex justify-between items-center mw8 center" {
                        a href="/" class="link orange f4 fw6" { "Corium" }
                        div class="flex gap3" {
                            a href="/login" class="link light-silver f6" { "Login" }
                            a href="/signup" class="f6 link br2 ph2 pv1 dib orange ba b--orange" { "Sign Up" }
                        }
                    }
                }
                main {
                    (content)
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(full_page.into_string())
}

// --- Auth Pages ---

#[get("/login")]
async fn login_page(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let user = get_user_from_request(&req, &db).await;
    if user.is_some() {
        return HttpResponse::Found()
            .append_header(("Location", "/dashboard"))
            .finish();
    }
    let content = html! {
        div class="pa4 flex justify-center" {
            div class="mw7 w-100" {
                h1 class="f2 fw6 mb4 tc orange" { "Log In" }
                form method="post" action="/login" class="ba b--dark-gray pa4 bg-near-black br2 " {
                    div class="mb3" {
                        label class="f6 b db mb2 near-white" for="email" { "Email" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="email" name="email" id="email" required placeholder="you@example.com" {}
                    }
                    div class="mb4" {
                        label class="f6 b db mb2 near-white" for="password" { "Password" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="password" name="password" id="password" required placeholder="Your password" {}
                    }
                    button type="submit" class="w-100 f5 link dim br2 ph3 pv3 dib near-black bg-orange pointer" {
                        "Log In"
                    }
                }
                p class="f6 tc mt3 light-silver" {
                    "Don't have an account? "
                    a href="/signup" class="link hot-pink underline" { "Sign up" }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Log In - Corium", content, None).into_string())
}

#[derive(Deserialize)]
struct LoginForm {
    email: String,
    password: String,
}

#[post("/login")]
async fn login_handler(db: Data<Arc<DB>>, form: Form<LoginForm>) -> impl Responder {
    let form = form.into_inner();
    let user = match db.get_user_by_email(&form.email).await {
        Some(u) => u,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/login?error=invalid"))
                .finish();
        }
    };

    if !user.verify_password(&form.password) {
        return HttpResponse::Found()
            .append_header(("Location", "/login?error=invalid"))
            .finish();
    }

    let session = Session::new(&user.id);
    let max_age = session.cookie_max_age_secs();
    let token = session.token.clone();

    if db.save_session(&session).await.is_err() {
        return HttpResponse::InternalServerError().body("Failed to create session");
    }

    info!("User {} logged in", user.email);

    HttpResponse::Found()
        .cookie(set_session_cookie(&token, max_age))
        .append_header(("Location", "/dashboard"))
        .finish()
}

#[get("/signup/ticket")]
async fn signup_ticket_page() -> impl Responder {
    let content = html! {
        div class="pa4 flex justify-center" {
            div class="mw7 w-100" {
                h1 class="f2 fw6 mb4 tc orange" { "Get Invite Code" }
                form method="post" action="/signup/ticket" class="ba b--dark-gray pa4 bg-near-black br2 " {
                    div class="mb4" {
                        label class="f6 b db mb2 near-white" for="api_key" { "Admin API Key" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="password" name="api_key" id="api_key" required placeholder="Enter the admin API key" {}
                        small class="f6 orange db mt1" { "Contact your administrator for the API key" }
                    }
                    button type="submit" class="w-100 f5 link dim br2 ph3 pv3 dib near-black bg-orange pointer" {
                        "Get Invite Code"
                    }
                }
                p class="f6 tc mt3 light-silver" {
                    "Already have a code? "
                    a href="/signup" class="link hot-pink underline" { "Sign up" }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Get Invite Code - Corium", content, None).into_string())
}

#[derive(Deserialize)]
struct SignupTicketForm {
    api_key: String,
}

#[post("/signup/ticket")]
async fn signup_ticket_handler(
    db: Data<Arc<DB>>,
    config: Data<Arc<Server>>,
    form: Form<SignupTicketForm>,
) -> impl Responder {
    let form = form.into_inner();

    if form.api_key != config.admin_api_key() {
        return HttpResponse::Found()
            .append_header(("Location", "/signup/ticket?error=invalid_key"))
            .finish();
    }

    let ticket = Ticket::new("admin_api");
    let code = ticket.code.clone();
    if let Err(e) = db.save_ticket(&ticket).await {
        error!("Failed to create ticket: {}", e);
        return HttpResponse::InternalServerError().body("Failed to create ticket");
    }

    info!("Ticket generated via signup page: {}", code);

    let content = html! {
        div class="pa4 flex justify-center" {
            div class="mw7 w-100" {
                h1 class="f2 fw6 mb4 tc orange" { "Your Invite Code" }
                div class="ba b--dark-gray pa4 bg-near-black br2 tc" {
                    p class="f6 mb2 light-silver" { "Use this code to sign up:" }
                    code class="f3 fw7 orange" { (code) }
                    p class="f6 mt3 light-silver" { "This code can only be used once. Keep it safe." }
                }
                div class="tc mt4" {
                    a href="/signup" class="f5 link dim br2 ph3 pv2 dib near-black bg-orange" { "Sign Up Now" }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Invite Code - Corium", content, None).into_string())
}

#[get("/signup")]
async fn signup_page(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let user = get_user_from_request(&req, &db).await;
    if user.is_some() {
        return HttpResponse::Found()
            .append_header(("Location", "/dashboard"))
            .finish();
    }

    let content = html! {
        div class="pa4 flex justify-center" {
            div class="mw7 w-100" {
                h1 class="f2 fw6 mb4 tc orange" { "Sign Up" }
                form method="post" action="/signup" class="ba b--dark-gray pa4 bg-near-black br2 " {
                    div class="mb3" {
                        label class="f6 b db mb2 near-white" for="ticket" { "Invite Code" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="text" name="ticket" id="ticket" required placeholder="Enter your invite code" {}
                        small class="f6 orange db mt1" {
                            "Need a code? "
                            a href="/signup/ticket" class="link hot-pink underline" { "Get one with an API key" }
                        }
                    }
                    div class="mb3" {
                        label class="f6 b db mb2 near-white" for="email" { "Email" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="email" name="email" id="email" required placeholder="you@example.com" {}
                    }
                    div class="mb3" {
                        label class="f6 b db mb2 near-white" for="password" { "Password" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="password" name="password" id="password" required minlength="8" placeholder="At least 8 characters" {}
                    }
                    div class="mb4" {
                        label class="f6 b db mb2 near-white" for="password_confirm" { "Confirm Password" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="password" name="password_confirm" id="password_confirm" required placeholder="Repeat your password" {}
                    }
                    button type="submit" class="w-100 f5 link dim br2 ph3 pv3 dib near-black bg-orange pointer" {
                        "Create Account"
                    }
                }
                p class="f6 tc mt3 light-silver" {
                    "Already have an account? "
                    a href="/login" class="link hot-pink underline" { "Log in" }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Sign Up - Corium", content, None).into_string())
}

#[derive(Deserialize)]
struct SignupForm {
    email: String,
    password: String,
    password_confirm: String,
    ticket: String,
}

#[post("/signup")]
async fn signup_handler(db: Data<Arc<DB>>, form: Form<SignupForm>) -> impl Responder {
    let form = form.into_inner();

    if form.password != form.password_confirm {
        return HttpResponse::Found()
            .append_header(("Location", "/signup?error=password_mismatch"))
            .finish();
    }

    if form.password.len() < 8 {
        return HttpResponse::Found()
            .append_header(("Location", "/signup?error=password_short"))
            .finish();
    }

    if db.get_user_by_email(&form.email).await.is_some() {
        return HttpResponse::Found()
            .append_header(("Location", "/signup?error=email_taken"))
            .finish();
    }

    let ticket = match db.get_ticket_by_code(&form.ticket).await {
        Some(t) => t,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/signup?error=invalid_ticket"))
                .finish();
        }
    };

    if ticket.used_by.is_some() {
        return HttpResponse::Found()
            .append_header(("Location", "/signup?error=ticket_used"))
            .finish();
    }

    let user = match User::new(&form.email, &form.password) {
        Ok(u) => u,
        Err(e) => {
            error!("Failed to create user: {}", e);
            return HttpResponse::Found()
                .append_header(("Location", "/signup?error=unknown"))
                .finish();
        }
    };

    if db.save_user(&user).await.is_err() {
        return HttpResponse::InternalServerError().body("Failed to save user");
    }

    if let Err(e) = db.use_ticket(&form.ticket, &user.id).await {
        error!("Failed to mark ticket as used: {}", e);
    }

    let session = Session::new(&user.id);
    let max_age = session.cookie_max_age_secs();
    let token = session.token.clone();

    if db.save_session(&session).await.is_err() {
        return HttpResponse::InternalServerError().body("Failed to create session");
    }

    info!("User {} signed up", user.email);

    HttpResponse::Found()
        .cookie(set_session_cookie(&token, max_age))
        .append_header(("Location", "/dashboard"))
        .finish()
}

#[get("/logout")]
async fn logout_handler(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    if let Some(cookie) = req.cookie(SESSION_COOKIE) {
        let token = cookie.value().to_string();
        let _ = db.delete_session(&token).await;
    }

    HttpResponse::Found()
        .cookie(clear_session_cookie())
        .append_header(("Location", "/"))
        .finish()
}

// --- Admin Auth (API Key based) ---

#[get("/admin/login")]
async fn admin_login_page() -> impl Responder {
    let content = html! {
        div class="pa4 flex justify-center" {
            div class="mw7 w-100" {
                h1 class="f2 fw6 mb4 tc hot-pink" { "Admin Login" }
                form method="post" action="/admin/login" class="ba b--dark-gray pa4 bg-near-black br2 " {
                    div class="mb4" {
                        label class="f6 b db mb2 near-white" for="api_key" { "Admin API Key" }
                        input
                            class="input-reset ba b--hot-pink pa2 db w-100"
                            type="password" name="api_key" id="api_key" required placeholder="Enter admin API key" {}
                        small class="f6 orange db mt1" { "Set via ADMIN_API_KEY environment variable" }
                    }
                    button type="submit" class="w-100 f5 link dim br2 ph3 pv3 dib near-black bg-hot-pink pointer" {
                        "Login as Admin"
                    }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Admin Login - Corium", content, None).into_string())
}

#[derive(Deserialize)]
struct AdminLoginForm {
    api_key: String,
}

#[post("/admin/login")]
async fn admin_login_handler(config: Data<Arc<Server>>, form: Form<AdminLoginForm>) -> impl Responder {
    let form = form.into_inner();

    if form.api_key != config.admin_api_key() {
        return HttpResponse::Found()
            .append_header(("Location", "/admin/login?error=invalid"))
            .finish();
    }

    info!("Admin authenticated via API key");

    HttpResponse::Found()
        .cookie(set_admin_cookie(config.admin_api_key()))
        .append_header(("Location", "/admin/tickets"))
        .finish()
}

#[get("/admin/logout")]
async fn admin_logout_handler() -> impl Responder {
    HttpResponse::Found()
        .cookie(clear_admin_cookie())
        .append_header(("Location", "/"))
        .finish()
}

// --- Dashboard ---

#[get("/dashboard")]
async fn dashboard(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let user = match get_user_from_request(&req, &db).await {
        Some(u) => u,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/login"))
                .finish();
        }
    };

    let codes = db.list_codes_by_user(&user.id).await;
    let endpoint_count = codes.len();

    let cards: Vec<Markup> = codes.iter().map(|code| {
        let edit_url = format!("/endpoints/edit/{}", code.id());
        let run_url = format!("/run{}", code.path());
        let delete_url = format!("/api/endpoints{}", code.path());
        let target_id = format!("endpoint-{}", code.id());
        let lang_str = match code.lang() {
            Lang::Kotlin => "Kotlin",
            Lang::Rust => "Rust",
        };

        html! {
            div class="fl w-100 w-50-m w-33-l pa2" id=(target_id) {
                div class="ba b--dark-gray pa3 bg-black near-white br2 " {
                    h3 class="f4 fw6 mb2 truncate orange" { (code.path()) }
                    div class="f6 gray mb2" { "Language: " (lang_str) }
                    div class="f7 gray mb2" { "Updated: " (format_timestamp(code.updated_at())) }
                    div class="flex gap2 mt2" {
                        a href=(edit_url) class="f6 link dim br2 ba bw1 ph2 pv1 dib orange" { "Edit" }
                        a href=(run_url) class="f6 link dim br2 ba bw1 ph2 pv1 dib hot-pink" { "Run" }
                        button
                            hx-delete=(delete_url)
                            hx-confirm="Are you sure you want to delete this endpoint?"
                            hx-target={"#" (target_id)}
                            hx-swap="outerHTML"
                            class="f6 link dim br2 ba bw1 ph2 pv1 dib dark-red pointer" {
                            "Delete"
                        }
                    }
                }
            }
        }
    }).collect();

    let content = html! {
        div class="pa4 mw8 center" {
            div class="flex justify-between items-center mb4" {
                div {
                    h1 class="f2 fw6 mb0 orange" { "Dashboard" }
                    p class="f6 light-silver" { "Welcome back, " (user.email) }
                }
                a href="/endpoints/create" class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange" {
                    "+ New Endpoint"
                }
            }

            div class="flex gap3 mb4" {
                div class="ba b--dark-gray pa3 bg-black near-white br2 w-100 w-25-l" {
                    h3 class="f6 ttu gray mb1" { "Endpoints" }
                    p class="f2 fw6 orange mb0" { (endpoint_count) }
                }
                a href="/endpoints/create" class="ba b--dark-gray pa3 bg-black near-white br2 w-100 w-25-l link near-white" {
                    h3 class="f6 ttu gray mb1" { "Create" }
                    p class="f2 fw6 hot-pink mb0" { "+" }
                }
                a href="/endpoints" class="ba b--dark-gray pa3 bg-black near-white br2 w-100 w-25-l link near-white" {
                    h3 class="f6 ttu gray mb1" { "View All" }
                    p class="f2 fw6 hot-pink mb0" { (PreEscaped("→")) }
                }
                a href="/settings" class="ba b--dark-gray pa3 bg-black near-white br2 w-100 w-25-l link near-white" {
                    h3 class="f6 ttu gray mb1" { "Settings" }
                    p class="f2 fw6 orange mb0" { (PreEscaped("⚙")) }
                }
            }

            @if cards.is_empty() {
                div class="pa5 ba b--dark-gray bg-black near-white br2 tc" {
                    p class="f4 gray mb3" { "No endpoints yet" }
                    a href="/endpoints/create" class="f6 link dim br2 ph3 pv2 dib near-black bg-orange" {
                        "Create your first endpoint"
                    }
                }
            } @else {
                h2 class="f4 fw6 mb3 orange" { "Your Endpoints" }
                div class="cf" {
                    @for card in &cards {
                        (card)
                    }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Dashboard - Corium", content, Some(&user)).into_string())
}

// --- Endpoint Pages ---

#[get("/endpoints")]
async fn list_endpoints(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let user = match get_user_from_request(&req, &db).await {
        Some(u) => u,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/login"))
                .finish();
        }
    };

    let codes = db.list_codes_by_user(&user.id).await;

    let cards: Vec<Markup> = codes.iter().map(|code| {
        let edit_url = format!("/endpoints/edit/{}", code.id());
        let run_url = format!("/run{}", code.path());
        let delete_url = format!("/api/endpoints{}", code.path());
        let target_id = format!("endpoint-{}", code.id());
        let lang_str = match code.lang() {
            Lang::Kotlin => "Kotlin",
            Lang::Rust => "Rust",
        };

        html! {
            div class="fl w-100 w-50-m w-33-l pa2" id=(target_id) {
                div class="ba b--dark-gray pa3 bg-black near-white br2 " {
                    h3 class="f4 fw6 mb2 truncate orange" { (code.path()) }
                    div class="f6 gray mb2" { "Language: " (lang_str) }
                    div class="flex gap2 mt2" {
                        a href=(edit_url) class="f6 link dim br2 ba bw1 ph2 pv1 dib orange" { "Edit" }
                        a href=(run_url) class="f6 link dim br2 ba bw1 ph2 pv1 dib hot-pink" { "Run" }
                        button
                            hx-delete=(delete_url)
                            hx-confirm="Are you sure?"
                            hx-target={"#" (target_id)}
                            hx-swap="outerHTML"
                            class="f6 link dim br2 ba bw1 ph2 pv1 dib dark-red pointer" {
                            "Delete"
                        }
                    }
                }
            }
        }
    }).collect();

    let content = html! {
        div class="pa4 mw8 center" {
            div class="flex justify-between items-center mb4" {
                h1 class="f2 fw6 orange" { "Endpoints" }
                a href="/endpoints/create" class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange" { "+ New Endpoint" }
            }
            @if cards.is_empty() {
                div class="pa4 ba b--dark-gray bg-black near-white" {
                    p class="f5 gray" { "No endpoints yet. Create your first one!" }
                }
            } @else {
                div class="cf" {
                    @for card in &cards {
                        (card)
                    }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Endpoints - Corium", content, Some(&user)).into_string())
}

#[get("/endpoints/create")]
async fn create_endpoint_form(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let _user = match get_user_from_request(&req, &db).await {
        Some(u) => u,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/login"))
                .finish();
        }
    };

    let content = html! {
        div class="pa3" {
            h1 class="f2 fw6 mb3 orange" { "Create New Endpoint" }
            (endpoint_editor(None, DEFAULT_KOTLIN_CODE, "", "/api/endpoints", "post", false, &[], &[]))
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Create Endpoint - Corium", content, Some(&_user)).into_string())
}

#[get("/endpoints/edit/{id}")]
async fn edit_endpoint_form(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    path: Path<String>,
) -> impl Responder {
    let user = match get_user_from_request(&req, &db).await {
        Some(u) => u,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/login"))
                .finish();
        }
    };

    let id = path.into_inner();

    match db.load_code_by_id(&id).await {
        Some(code) => {
            if !code.user_id().is_empty() && code.user_id() != user.id {
                return HttpResponse::Forbidden().body("Not authorized");
            }
            let code_versions = db.list_code_versions(code.id()).await;
            let test_versions = db.list_test_versions(code.id()).await;
            let form_url = format!("/api/endpoints{}", code.path());
            let content = html! {
                div class="pa3" {
                    h1 class="f2 fw6 mb3 orange" { "Edit Endpoint" }
                    (endpoint_editor(Some(code.path()), code.raw(), code.hurl(), &form_url, "put", true, &code_versions, &test_versions))
                }
            };
            HttpResponse::Ok()
                .content_type("text/html")
                .body(layout("Edit Endpoint - Corium", content, Some(&user)).into_string())
        }
        None => HttpResponse::NotFound().body("Endpoint not found"),
    }
}

fn endpoint_editor(
    path: Option<&str>,
    code: &str,
    hurl: &str,
    form_url: &str,
    method: &str,
    show_actions: bool,
    code_versions: &[CodeVersion],
    test_versions: &[TestVersion],
) -> Markup {
    let escaped_code = html_escape(code);
    let escaped_hurl = html_escape(hurl);

    let code_versions_json = serde_json::to_string(code_versions).unwrap_or_else(|_| "[]".to_string());
    let test_versions_json = serde_json::to_string(test_versions).unwrap_or_else(|_| "[]".to_string());

    let path_val = path.unwrap_or("");

    html! {
        form
            id="endpoint-form"
            hx-post=(form_url)
            hx-ext="response-targets"
            hx-target-error="#endpoint-error"
            class="w-100"
        {
            div id="endpoint-error" class="dark-red mb2" {}
            div class="flex gap3 mb2" {
                div class="flex-auto" {
                    label class="f6 b db mb2 near-white" for="path" { "Endpoint Path" }
                    input
                        class="input-reset ba b--orange pa2 db w-100"
                        type="text" name="path" id="path" placeholder="/api/hello"
                        value=(path_val)
                        required pattern="^/[a-zA-Z0-9_/-]+$"
                        title="Must start with / and contain only letters, numbers, underscores, hyphens, and forward slashes" {}
                }
                div class="w5" {
                    label class="f6 b db mb2 near-white" for="lang" { "Language" }
                    select class="db w-100 ba b--orange pa2" name="lang" id="lang" {
                        option value="kotlin" selected { "Kotlin (WASM)" }
                        option value="rust" { "Rust (WASM - coming soon)" }
                    }
                }
            }

            div class="flex gap2 mb2" {
                button type="button" class="tab-btn f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer" id="tab-code" onclick="switchTab('code')" { "Code" }
                button type="button" class="tab-btn f6 link dim br2 ba bw1 ph3 pv2 dib near-white bg-dark-gray pointer" id="tab-test" onclick="switchTab('test')" { "Test" }
            }

            div id="code-panel" {
                div class="mb2" {
                    label class="f6 b db mb2 near-white" for="code" { "Source Code" }
                    textarea
                        class="code-editor input-reset ba b--orange pa2 db w-100"
                        name="code" id="code" rows="20"
                        placeholder="Write your code here..."
                        style="min-height: 50vh; resize: vertical;"
                    { (PreEscaped(&escaped_code)) }
                }

                @if show_actions {
                    div class="flex gap3 mb2 items-center" {
                        select id="code-version-select" class="ba b--orange pa2 bg-near-black near-white" style="display:none" {
                        }
                        button
                            type="button"
                            id="save-version-btn"
                            class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer"
                            onclick="saveCodeVersion()" {
                            "Save Version"
                        }
                        button
                            type="button"
                            id="deploy-btn"
                            class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-green pointer"
                            onclick="deployEndpoint()" {
                            "Deploy"
                        }
                    }
                } @else {
                    button type="submit" class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer" {
                        "Create Endpoint"
                    }
                }
            }

            div id="test-panel" style="display:none" {
                div class="flex gap3 mb2" {
                    div class="flex-auto" {
                        label class="f6 b db mb2 near-white" { "Hurl Test" }
                        textarea
                            class="code-editor input-reset ba b--orange pa2 db w-100"
                            name="hurl" id="hurl" rows="6"
                            placeholder=(PreEscaped("GET /api/hello?name=World\nor key=value per line"))
                        { (PreEscaped(&escaped_hurl)) }
                    }
                    div class="flex flex-column gap2 justify-end" {
                        button
                            type="button"
                            id="test-btn"
                            class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-hot-pink pointer"
                            onclick="testEndpoint()" {
                            "Run Test"
                        }
                    }
                }

                div class="mb2" {
                    label class="f6 b db mb2 near-white" { "Output" }
                    div id="test-result" class="output-panel ba b--dark-gray br2 pa3 overflow-auto" style="min-height: 30vh;" {
                        p class="f6 gray" { "Run a test to see results" }
                    }
                }

                @if show_actions {
                    div class="flex gap3 items-center" {
                        select id="test-version-select" class="ba b--orange pa2 bg-near-black near-white" style="display:none" {
                        }
                        button
                            type="button"
                            id="save-test-btn"
                            class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer"
                            onclick="saveTestVersion()" {
                            "Save Test"
                        }
                    }
                }
            }

            @if method == "put" {
                input type="hidden" name="_method" value="PUT" {}
            }
        }

        script {
            (PreEscaped(format!(r#"
                var codeVersions = {code_versions_json};
                var testVersions = {test_versions_json};
                var endpointPath = '{path_val}';

                function switchTab(tab) {{
                    var codePanel = document.getElementById('code-panel');
                    var testPanel = document.getElementById('test-panel');
                    var tabCode = document.getElementById('tab-code');
                    var tabTest = document.getElementById('tab-test');

                    if (tab === 'code') {{
                        codePanel.style.display = '';
                        testPanel.style.display = 'none';
                        tabCode.className = 'tab-btn f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer';
                        tabTest.className = 'tab-btn f6 link dim br2 ba bw1 ph3 pv2 dib near-white bg-dark-gray pointer';
                    }} else {{
                        codePanel.style.display = 'none';
                        testPanel.style.display = '';
                        tabCode.className = 'tab-btn f6 link dim br2 ba bw1 ph3 pv2 dib near-white bg-dark-gray pointer';
                        tabTest.className = 'tab-btn f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer';
                    }}
                }}

                function populateVersionSelects() {{
                    var codeSelect = document.getElementById('code-version-select');
                    var testSelect = document.getElementById('test-version-select');

                    if (codeVersions.length > 0) {{
                        codeSelect.style.display = '';
                        codeSelect.innerHTML = '<option value="">-- Load version --</option>';
                        codeVersions.forEach(function(v) {{
                            var opt = document.createElement('option');
                            opt.value = v.id;
                            opt.textContent = 'v' + v.version + ' (' + new Date(v.created_at * 1000).toLocaleString() + ')';
                            codeSelect.appendChild(opt);
                        }});
                        codeSelect.onchange = function() {{
                            var v = codeVersions.find(function(v) {{ return v.id === codeSelect.value; }});
                            if (v) {{
                                document.getElementById('code').value = v.raw;
                            }}
                        }};
                    }}

                    if (testVersions.length > 0) {{
                        testSelect.style.display = '';
                        testSelect.innerHTML = '<option value="">-- Load version --</option>';
                        testVersions.forEach(function(v) {{
                            var opt = document.createElement('option');
                            opt.textContent = 'v' + v.version + ': ' + v.hurl.substring(0, 40) + (v.hurl.length > 40 ? '...' : '');
                            opt.value = v.id;
                            testSelect.appendChild(opt);
                        }});
                        testSelect.onchange = function() {{
                            var v = testVersions.find(function(v) {{ return v.id === testSelect.value; }});
                            if (v) {{
                                document.getElementById('hurl').value = v.hurl;
                            }}
                        }};
                    }}
                }}

                populateVersionSelects();

                function testEndpoint() {{
                    var pathEl = document.getElementById('path');
                    var hurlEl = document.getElementById('hurl');
                    var resultEl = document.getElementById('test-result');

                    if (!pathEl || !hurlEl) return;

                    var path = pathEl.value;
                    var hurl = hurlEl.value;
                    var testPath = '/run' + path;

                    var params = new URLSearchParams();
                    var lines = hurl.split('\\n');
                    for (var i = 0; i < lines.length; i++) {{
                        var line = lines[i].trim();
                        if (line.indexOf('=') > 0 && line.indexOf('GET') !== 0 && line.indexOf('POST') !== 0) {{
                            var eqIdx = line.indexOf('=');
                            var key = line.substring(0, eqIdx).trim();
                            var val = line.substring(eqIdx + 1).trim();
                            if (key) params.append(key, val);
                        }}
                    }}

                    if (lines.length > 0) {{
                        var firstLine = lines[0].trim();
                        var qIdx = firstLine.indexOf('?');
                        if (qIdx > 0) {{
                            var queryString = firstLine.substring(qIdx + 1);
                            var pairs = queryString.split('&');
                            for (var j = 0; j < pairs.length; j++) {{
                                var pair = pairs[j];
                                var eqPos = pair.indexOf('=');
                                if (eqPos > 0) {{
                                    params.append(pair.substring(0, eqPos), pair.substring(eqPos + 1));
                                }}
                            }}
                        }}
                    }}

                    var url = testPath;
                    if (params.toString()) {{
                        url += '?' + params.toString();
                    }}

                    resultEl.innerHTML = '<p class="f6 orange">Running test...</p>';

                    fetch(url)
                        .then(function(r) {{ return r.json(); }})
                        .then(function(data) {{
                            var output = JSON.stringify(data, null, 2);
                            var status = data.status || '—';
                            var statusColor = status >= 200 && status < 300 ? 'green' : 'dark-red';
                            resultEl.innerHTML = '<div class="flex items-center gap2 mb2"><span class="f6 b gray">Status:</span><span class="f6 b ' + statusColor + '">' + status + '</span></div><pre class="f6 code-editor overflow-auto" style="background:transparent;border:none;padding:0;white-space:pre-wrap;">' + output.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</pre>';
                        }})
                        .catch(function(err) {{
                            resultEl.innerHTML = '<h4 class="f6 b mb2 dark-red">Error</h4><p class="f6 dark-red">' + err.message + '</p>';
                        }});
                }}

                function saveCodeVersion() {{
                    var pathEl = document.getElementById('path');
                    var codeEl = document.getElementById('code');
                    var langEl = document.getElementById('lang');

                    if (!pathEl) return;

                    var endpointId = '';
                    var pathParts = window.location.pathname.split('/');
                    endpointId = pathParts[pathParts.length - 1];

                    var btn = document.getElementById('save-version-btn');
                    btn.textContent = 'Saving...';
                    btn.disabled = true;

                    var form = document.getElementById('endpoint-form');
                    var formData = new FormData();
                    formData.append('path', pathEl.value);
                    formData.append('lang', langEl ? langEl.value : 'kotlin');
                    formData.append('code', codeEl ? codeEl.value : '');
                    formData.append('_method', 'PUT');

                    fetch(form.action, {{
                        method: 'POST',
                        body: formData,
                        headers: {{ 'X-Requested-With': 'XMLHttpRequest' }}
                    }})
                    .then(function(r) {{
                        if (r.ok) {{
                            btn.textContent = 'Saved!';
                            setTimeout(function() {{
                                btn.textContent = 'Save Version';
                                btn.disabled = false;
                                window.location.reload();
                            }}, 1000);
                        }} else {{
                            btn.textContent = 'Save Version';
                            btn.disabled = false;
                            return r.text();
                        }}
                    }})
                    .catch(function(err) {{
                        btn.textContent = 'Save Version';
                        btn.disabled = false;
                    }});
                }}

                function saveTestVersion() {{
                    var hurlEl = document.getElementById('hurl');

                    var pathParts = window.location.pathname.split('/');
                    var endpointId = pathParts[pathParts.length - 1];

                    var btn = document.getElementById('save-test-btn');
                    btn.textContent = 'Saving...';
                    btn.disabled = true;

                    fetch('/api/endpoints/' + endpointId + '/test-versions', {{
                        method: 'POST',
                        headers: {{ 'Content-Type': 'application/x-www-form-urlencoded' }},
                        body: 'hurl=' + encodeURIComponent(hurlEl ? hurlEl.value : ''),
                    }})
                    .then(function(r) {{
                        if (r.ok) {{
                            btn.textContent = 'Saved!';
                            setTimeout(function() {{
                                btn.textContent = 'Save Test';
                                btn.disabled = false;
                                window.location.reload();
                            }}, 1000);
                        }} else {{
                            btn.textContent = 'Save Test';
                            btn.disabled = false;
                        }}
                    }})
                    .catch(function(err) {{
                        btn.textContent = 'Save Test';
                        btn.disabled = false;
                    }});
                }}

                function deployEndpoint() {{
                    var resultEl = document.getElementById('test-result');
                    var btn = document.getElementById('deploy-btn');
                    btn.textContent = 'Deploying...';
                    setTimeout(function() {{
                        btn.textContent = 'Deploy';
                        switchTab('test');
                        resultEl.innerHTML = '<div class="flex items-center gap2 mb2"><span class="f6 b orange">Deploy</span></div><p class="f6 gray">Deploy functionality coming soon.</p>';
                    }}, 1000);
                }}
            "#, code_versions_json = code_versions_json, test_versions_json = test_versions_json, path_val = path_val)))
        }
    }
}

// --- API Handlers ---

#[derive(Deserialize)]
struct CreateEndpointForm {
    path: String,
    lang: String,
    code: String,
    hurl: Option<String>,
}

#[post("/api/endpoints")]
async fn create_endpoint(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    compiler: Data<Arc<Compiler>>,
    form: Form<CreateEndpointForm>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => {
            return HttpResponse::Unauthorized()
                .content_type("text/html")
                .body(html! { div class="dark-red" { "Not authenticated" } }.into_string());
        }
    };

    let form = form.into_inner();

    if !form.path.starts_with('/') {
        return HttpResponse::BadRequest()
            .content_type("text/html")
            .body(html! { div class="dark-red" { "Path must start with /" } }.into_string());
    }

    if db.load_code(&form.path).await.is_some() {
        return HttpResponse::Conflict()
            .content_type("text/html")
            .body(html! { div class="dark-red" { "Endpoint with this path already exists" } }.into_string());
    }

    let lang = match form.lang.as_str() {
        "kotlin" => Lang::Kotlin,
        "rust" => Lang::Rust,
        _ => Lang::Kotlin,
    };

    match compiler.compile(&form.code, lang.clone()).await {
        Ok(wasm_bytes) => {
            let mut code = Code::new(&form.path, &form.code, lang.clone(), wasm_bytes.clone());
            code.user_id = user_id;
            code.hurl = form.hurl.clone().unwrap_or_default();
            match db.save_code(&code).await {
                Ok(_) => {
                    let code_version = CodeVersion::new(code.id(), 1, &form.code, lang, wasm_bytes);
                    let _ = db.save_code_version(&code_version).await;
                    if let Some(ref hurl) = form.hurl {
                        if !hurl.is_empty() {
                            let test_version = TestVersion::new(code.id(), 1, hurl);
                            let _ = db.save_test_version(&test_version).await;
                        }
                    }
                    info!("Created endpoint at {}", form.path);
                    let edit_url = format!("/endpoints/edit/{}", code.id());
                    HttpResponse::Created()
                        .append_header(("HX-Redirect", edit_url.clone()))
                        .content_type("text/html")
                        .body(html! { div { "saved" } }.into_string())
                }
                Err(e) => {
                    error!("Failed to save endpoint: {}", e);
                    HttpResponse::InternalServerError()
                        .content_type("text/html")
                        .body(html! { div class="dark-red" { "Failed to save: " (e) } }.into_string())
                }
            }
        }
        Err(e) => {
            error!("Compilation failed: {}", e);
            HttpResponse::BadRequest()
                .content_type("text/html")
                .body(html! { div class="dark-red" { "Compilation failed: " (e) } }.into_string())
        }
    }
}

#[derive(Deserialize)]
struct UpdateEndpointForm {
    path: String,
    lang: String,
    code: String,
    hurl: Option<String>,
    #[serde(rename = "_method")]
    _method: Option<String>,
}

#[post("/api/endpoints/{path:.*}")]
async fn update_endpoint(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    compiler: Data<Arc<Compiler>>,
    path: Path<String>,
    form: Form<UpdateEndpointForm>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => {
            return HttpResponse::Unauthorized()
                .content_type("text/html")
                .body(html! { div class="dark-red" { "Not authenticated" } }.into_string());
        }
    };

    let path = format!("/{}", path.into_inner());
    let form = form.into_inner();

    if form._method.as_deref() != Some("PUT") {
        return HttpResponse::MethodNotAllowed().finish();
    }

    let mut existing = match db.load_code(&path).await {
        Some(code) => code,
        None => return HttpResponse::NotFound()
            .content_type("text/html")
            .body(html! { div class="dark-red" { "Endpoint not found" } }.into_string()),
    };

    if !existing.user_id().is_empty() && existing.user_id() != user_id {
        return HttpResponse::Forbidden()
            .content_type("text/html")
            .body(html! { div class="dark-red" { "Not authorized" } }.into_string());
    }

    let lang = match form.lang.as_str() {
        "kotlin" => Lang::Kotlin,
        "rust" => Lang::Rust,
        _ => Lang::Kotlin,
    };

    match compiler.compile(&form.code, lang.clone()).await {
        Ok(wasm_bytes) => {
            let version_number = db.next_code_version_number(existing.id()).await;
            let code_version = CodeVersion::new(existing.id(), version_number, &form.code, lang.clone(), wasm_bytes.clone());
            let _ = db.save_code_version(&code_version).await;

            existing.set_path(&form.path);
            existing.set_revision(&form.code, lang, wasm_bytes);
            existing.hurl = form.hurl.unwrap_or_default();
            match db.save_code(&existing).await {
                Ok(_) => {
                    info!("Updated endpoint at {}", form.path);
                    let edit_url = format!("/endpoints/edit/{}", existing.id());
                    HttpResponse::Ok()
                        .append_header(("HX-Redirect", edit_url.clone()))
                        .content_type("text/html")
                        .body(html! { div { "saved" } }.into_string())
                }
                Err(e) => {
                    error!("Failed to update endpoint: {}", e);
                    HttpResponse::InternalServerError()
                        .content_type("text/html")
                        .body(html! { div class="dark-red" { "Failed to save: " (e) } }.into_string())
                }
            }
        }
        Err(e) => {
            error!("Compilation failed: {}", e);
            HttpResponse::BadRequest()
                .content_type("text/html")
                .body(html! { div class="dark-red" { "Compilation failed: " (e) } }.into_string())
        }
    }
}

#[delete("/api/endpoints/{path:.*}")]
async fn delete_endpoint(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    path: Path<String>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized()
            .content_type("text/html")
            .body(html! { div class="dark-red" { "Not authenticated" } }.into_string()),
    };

    let path = format!("/{}", path.into_inner());

    if let Some(code) = db.load_code(&path).await {
        if !code.user_id().is_empty() && code.user_id() != user_id {
            return HttpResponse::Forbidden()
                .content_type("text/html")
                .body(html! { div class="dark-red" { "Not authorized" } }.into_string());
        }
    }

    if db.delete_code(&path).await {
        HttpResponse::Ok()
            .content_type("text/html")
            .body(html! { div { "deleted" } }.into_string())
    } else {
        HttpResponse::NotFound()
            .content_type("text/html")
            .body(html! { div { "Endpoint not found" } }.into_string())
    }
}

#[derive(Deserialize)]
struct TestEndpointForm {
    hurl: String,
}

#[post("/api/endpoints/{id}/test")]
async fn test_endpoint(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    runner: Data<Arc<Runner>>,
    path: Path<String>,
    form: Form<TestEndpointForm>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let id = path.into_inner();

    let code = match db.load_code_by_id(&id).await {
        Some(c) => c,
        None => return HttpResponse::NotFound().json(serde_json::json!({"error": "Endpoint not found"})),
    };

    if code.user_id() != user_id && !code.user_id().is_empty() {
        return HttpResponse::Forbidden().json(serde_json::json!({"error": "Not authorized"}));
    }

    let hurl_content = form.into_inner().hurl;
    let params = match HurlTest::parse(&hurl_content) {
        Ok(test) => test.params,
        Err(_) => corium::auth::parse_simple_params(&hurl_content),
    };

    let params_json = serde_json::to_string(&params).unwrap_or_default();

    match runner.run(&code, &params_json) {
        Ok(result) => HttpResponse::Ok().json(serde_json::json!({
            "status": result.status,
            "output": result.output,
            "path": code.path(),
            "lang": code.lang().as_str(),
        })),
        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
            "error": e.to_string(),
            "path": code.path(),
        })),
    }
}

#[derive(Deserialize)]
struct SaveCodeVersionForm {
    path: String,
    lang: String,
    code: String,
    #[serde(rename = "_method")]
    _method: Option<String>,
}

#[post("/api/endpoints/{id}/versions")]
async fn save_code_version(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    compiler: Data<Arc<Compiler>>,
    path: Path<String>,
    form: Form<SaveCodeVersionForm>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let id = path.into_inner();

    let mut existing = match db.load_code_by_id(&id).await {
        Some(c) => c,
        None => return HttpResponse::NotFound().json(serde_json::json!({"error": "Endpoint not found"})),
    };

    if !existing.user_id().is_empty() && existing.user_id() != user_id {
        return HttpResponse::Forbidden().json(serde_json::json!({"error": "Not authorized"}));
    }

    let form = form.into_inner();

    let lang = match form.lang.as_str() {
        "kotlin" => Lang::Kotlin,
        "rust" => Lang::Rust,
        _ => Lang::Kotlin,
    };

    match compiler.compile(&form.code, lang.clone()).await {
        Ok(wasm_bytes) => {
            let version_number = db.next_code_version_number(existing.id()).await;
            let version = CodeVersion::new(existing.id(), version_number, &form.code, lang.clone(), wasm_bytes.clone());
            let _ = db.save_code_version(&version).await;

            existing.set_path(&form.path);
            existing.set_revision(&form.code, lang, wasm_bytes);
            let _ = db.save_code(&existing).await;

            info!("Saved code version v{} for endpoint {}", version_number, existing.path());

            let edit_url = format!("/endpoints/edit/{}", existing.id());
            HttpResponse::Ok()
                .append_header(("HX-Redirect", edit_url.clone()))
                .content_type("text/html")
                .body(html! { div { "saved" } }.into_string())
        }
        Err(e) => {
            error!("Compilation failed: {}", e);
            HttpResponse::BadRequest()
                .content_type("text/html")
                .body(html! { div class="dark-red" { "Compilation failed: " (e) } }.into_string())
        }
    }
}

#[get("/api/endpoints/{id}/versions")]
async fn list_code_versions(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    path: Path<String>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let id = path.into_inner();

    let code = match db.load_code_by_id(&id).await {
        Some(c) => c,
        None => return HttpResponse::NotFound().json(serde_json::json!({"error": "Endpoint not found"})),
    };

    if !code.user_id().is_empty() && code.user_id() != user_id {
        return HttpResponse::Forbidden().json(serde_json::json!({"error": "Not authorized"}));
    }

    let versions = db.list_code_versions(&id).await;
    HttpResponse::Ok().json(versions)
}

#[get("/api/endpoints/{id}/versions/{version_id}")]
async fn get_code_version(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    path: web::Path<(String, String)>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let (id, _version_id) = path.into_inner();

    let code = match db.load_code_by_id(&id).await {
        Some(c) => c,
        None => return HttpResponse::NotFound().json(serde_json::json!({"error": "Endpoint not found"})),
    };

    if !code.user_id().is_empty() && code.user_id() != user_id {
        return HttpResponse::Forbidden().json(serde_json::json!({"error": "Not authorized"}));
    }

    let versions = db.list_code_versions(&id).await;
    HttpResponse::Ok().json(versions)
}

#[derive(Deserialize)]
struct SaveTestVersionForm {
    hurl: String,
}

#[post("/api/endpoints/{id}/test-versions")]
async fn save_test_version(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    path: Path<String>,
    form: Form<SaveTestVersionForm>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let id = path.into_inner();

    let mut existing = match db.load_code_by_id(&id).await {
        Some(c) => c,
        None => return HttpResponse::NotFound().json(serde_json::json!({"error": "Endpoint not found"})),
    };

    if !existing.user_id().is_empty() && existing.user_id() != user_id {
        return HttpResponse::Forbidden().json(serde_json::json!({"error": "Not authorized"}));
    }

    let hurl_content = form.into_inner().hurl;
    let version_number = db.next_test_version_number(existing.id()).await;
    let version = TestVersion::new(existing.id(), version_number, &hurl_content);
    let _ = db.save_test_version(&version).await;

    existing.hurl = hurl_content;
    let _ = db.save_code(&existing).await;

    info!("Saved test version v{} for endpoint {}", version_number, existing.path());

    HttpResponse::Ok()
        .content_type("text/html")
        .body(html! { div { "saved" } }.into_string())
}

#[get("/api/endpoints/{id}/test-versions")]
async fn list_test_versions(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    path: Path<String>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let id = path.into_inner();

    let code = match db.load_code_by_id(&id).await {
        Some(c) => c,
        None => return HttpResponse::NotFound().json(serde_json::json!({"error": "Endpoint not found"})),
    };

    if !code.user_id().is_empty() && code.user_id() != user_id {
        return HttpResponse::Forbidden().json(serde_json::json!({"error": "Not authorized"}));
    }

    let versions = db.list_test_versions(&id).await;
    HttpResponse::Ok().json(versions)
}

#[get("/api/endpoints/{id}/test-versions/{version_id}")]
async fn get_test_version(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    path: web::Path<(String, String)>,
) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let (id, _version_id) = path.into_inner();

    let code = match db.load_code_by_id(&id).await {
        Some(c) => c,
        None => return HttpResponse::NotFound().json(serde_json::json!({"error": "Endpoint not found"})),
    };

    if !code.user_id().is_empty() && code.user_id() != user_id {
        return HttpResponse::Forbidden().json(serde_json::json!({"error": "Not authorized"}));
    }

    let versions = db.list_test_versions(&id).await;
    HttpResponse::Ok().json(versions)
}

// --- Settings ---

#[get("/settings")]
async fn settings_page(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let user = match get_user_from_request(&req, &db).await {
        Some(u) => u,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/login"))
                .finish();
        }
    };

    let content = html! {
        div class="pa4 mw8 center" {
            h1 class="f2 fw6 mb4 orange" { "Account Settings" }

            div class="ba b--dark-gray pa4 bg-near-black near-white br2 mb4" {
                h2 class="f4 fw6 mb3 orange" { "Profile" }
                div class="mb2" {
                    span class="f6 b gray" { "Email: " }
                    span class="f6 near-white" { (user.email) }
                }
                div {
                    span class="f6 b gray" { "Member since: " }
                    span class="f6 near-white" { (format_timestamp(user.created_at)) }
                }
            }

            div class="ba b--dark-gray pa4 bg-near-black near-white br2 " {
                h2 class="f4 fw6 mb3 hot-pink" { "Change Password" }
                form method="post" action="/settings/password" {
                    div class="mb3" {
                        label class="f6 b db mb2 near-white" for="current_password" { "Current Password" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="password" name="current_password" id="current_password" required {}
                    }
                    div class="mb3" {
                        label class="f6 b db mb2 near-white" for="new_password" { "New Password" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="password" name="new_password" id="new_password" required minlength="8" {}
                    }
                    div class="mb4" {
                        label class="f6 b db mb2 near-white" for="confirm_password" { "Confirm New Password" }
                        input
                            class="input-reset ba b--orange pa2 db w-100"
                            type="password" name="confirm_password" id="confirm_password" required {}
                    }
                    button type="submit" class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer" {
                        "Update Password"
                    }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Settings - Corium", content, Some(&user)).into_string())
}

#[derive(Deserialize)]
struct ChangePasswordForm {
    current_password: String,
    new_password: String,
    confirm_password: String,
}

#[post("/settings/password")]
async fn update_password(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    form: Form<ChangePasswordForm>,
) -> impl Responder {
    let user = match get_user_from_request(&req, &db).await {
        Some(u) => u,
        None => {
            return HttpResponse::Found()
                .append_header(("Location", "/login"))
                .finish();
        }
    };

    let form = form.into_inner();

    if form.new_password != form.confirm_password {
        return HttpResponse::Found()
            .append_header(("Location", "/settings?error=password_mismatch"))
            .finish();
    }

    if form.new_password.len() < 8 {
        return HttpResponse::Found()
            .append_header(("Location", "/settings?error=password_short"))
            .finish();
    }

    let mut user = match db.get_user_by_id(&user.id).await {
        Some(u) => u,
        None => return HttpResponse::NotFound().body("User not found"),
    };

    if !user.verify_password(&form.current_password) {
        return HttpResponse::Found()
            .append_header(("Location", "/settings?error=wrong_password"))
            .finish();
    }

    if let Err(e) = user.change_password(&form.new_password) {
        error!("Failed to hash password: {}", e);
        return HttpResponse::InternalServerError().body("Failed to update password");
    }

    if let Err(e) = db.update_user_password(&user.id, &user.password_hash).await {
        error!("Failed to update password: {}", e);
        return HttpResponse::InternalServerError().body("Failed to save password");
    }

    HttpResponse::Found()
        .append_header(("Location", "/settings?success=1"))
        .finish()
}

// --- Admin (API Key authenticated) ---

#[get("/admin/tickets")]
async fn admin_tickets_page(req: HttpRequest, db: Data<Arc<DB>>, config: Data<Arc<Server>>) -> impl Responder {
    if !validate_admin_api_key(&req, &config) {
        return HttpResponse::Found()
            .append_header(("Location", "/admin/login"))
            .finish();
    }

    let tickets = db.list_tickets().await;
    let ticket_rows: Vec<Markup> = tickets.iter().map(|t| {
        let status = if t.used_by.is_some() {
            html! { span class="f6 dark-red" { "Used" } }
        } else {
            html! { span class="f6 orange" { "Available" } }
        };
        let delete_url = format!("/admin/tickets/{}/delete", t.id);
        html! {
            tr {
                td class="pa2 bb b--dark-gray" { code class="f6 orange" { (t.code) } }
                td class="pa2 bb b--dark-gray" { (status) }
                td class="pa2 bb b--dark-gray near-white" { (format_timestamp(t.created_at)) }
                td class="pa2 bb b--dark-gray" {
                    form method="post" action=(delete_url) style="display:inline" {
                        button type="submit" class="f6 link dim br1 ba bw1 ph1 pv1 dib dark-red pointer" {
                            "Delete"
                        }
                    }
                }
            }
        }
    }).collect();

    let content = html! {
        div class="pa4 mw8 center" {
            div class="flex justify-between items-center mb4" {
                h1 class="f2 fw6 orange" { "Admin: Tickets" }
                div class="flex gap3" {
                    form method="post" action="/admin/tickets" {
                        button type="submit" class="f6 link dim br2 ba bw1 ph3 pv2 dib near-black bg-orange pointer" {
                            "Generate Ticket"
                        }
                    }
                    a href="/admin/logout" class="f6 link dim br2 ba bw1 ph3 pv2 dib hot-pink" { "Logout Admin" }
                }
            }
            @if ticket_rows.is_empty() {
                div class="pa4 ba b--dark-gray bg-black near-white" {
                    p class="f5 gray" { "No tickets yet. Generate one to invite new users." }
                }
            } @else {
                table class="w-100 collapse ba b--dark-gray bg-near-black near-white" {
                    thead {
                        tr class="bg-black" {
                            th class="pa2 tl f6 b bb b--dark-gray orange" { "Code" }
                            th class="pa2 tl f6 b bb b--dark-gray orange" { "Status" }
                            th class="pa2 tl f6 b bb b--dark-gray orange" { "Created" }
                            th class="pa2 tl f6 b bb b--dark-gray orange" { "Actions" }
                        }
                    }
                    tbody {
                        @for row in &ticket_rows {
                            (row)
                        }
                    }
                }
            }
        }
    };
    HttpResponse::Ok()
        .content_type("text/html")
        .body(layout("Admin - Tickets", content, None).into_string())
}

#[post("/admin/tickets")]
async fn create_ticket_handler(req: HttpRequest, db: Data<Arc<DB>>, config: Data<Arc<Server>>) -> impl Responder {
    if !validate_admin_api_key(&req, &config) {
        return HttpResponse::Found()
            .append_header(("Location", "/admin/login"))
            .finish();
    }

    let ticket = Ticket::new("admin");
    if let Err(e) = db.save_ticket(&ticket).await {
        error!("Failed to create ticket: {}", e);
        return HttpResponse::InternalServerError().body("Failed to create ticket");
    }

    info!("Admin created ticket {}", ticket.code);

    HttpResponse::Found()
        .append_header(("Location", "/admin/tickets"))
        .finish()
}

#[post("/admin/tickets/{id}/delete")]
async fn delete_ticket_handler(
    req: HttpRequest,
    db: Data<Arc<DB>>,
    config: Data<Arc<Server>>,
    path: Path<String>,
) -> impl Responder {
    if !validate_admin_api_key(&req, &config) {
        return HttpResponse::Found()
            .append_header(("Location", "/admin/login"))
            .finish();
    }

    let id = path.into_inner();
    db.delete_ticket(&id).await;

    HttpResponse::Found()
        .append_header(("Location", "/admin/tickets"))
        .finish()
}

// --- Public Endpoints ---

#[get("/wasm/{path:.*}")]
async fn serve_wasm(db: Data<Arc<DB>>, path: Path<String>) -> impl Responder {
    let path = format!("/{}", path.into_inner());

    match db.load_code(&path).await {
        Some(code) => HttpResponse::Ok()
            .content_type("application/wasm")
            .body(code.wasm().to_vec()),
        None => HttpResponse::NotFound().body("WASM endpoint not found"),
    }
}

#[get("/run/{path:.*}")]
async fn run_endpoint(
    db: Data<Arc<DB>>,
    runner: Data<Arc<Runner>>,
    path: Path<String>,
    query: Query<std::collections::HashMap<String, String>>,
) -> impl Responder {
    let endpoint_path = format!("/{}", path.into_inner());
    let params = serde_json::to_string(&query.into_inner()).unwrap_or_default();

    match db.load_code(&endpoint_path).await {
        Some(code) => {
            match runner.run(&code, &params) {
                Ok(result) => HttpResponse::Ok().json(serde_json::json!({
                    "status": result.status,
                    "output": result.output,
                    "path": endpoint_path,
                    "lang": code.lang().as_str(),
                })),
                Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
                    "error": e.to_string(),
                    "path": endpoint_path,
                })),
            }
        }
        None => HttpResponse::NotFound().json(serde_json::json!({
            "error": "Endpoint not found",
            "path": endpoint_path,
        })),
    }
}

#[get("/api/endpoints")]
async fn list_endpoints_api(req: HttpRequest, db: Data<Arc<DB>>) -> impl Responder {
    let user_id = match get_user_id_from_request(&req, &db).await {
        Some(id) => id,
        None => return HttpResponse::Unauthorized().json(serde_json::json!({"error": "Not authenticated"})),
    };

    let codes = db.list_codes_by_user(&user_id).await;
    HttpResponse::Ok().json(codes)
}

#[get("/health")]
async fn health() -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({"status": "ok"}))
}

#[get("/up")]
async fn up() -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({"status": "up"}))
}