Skip to content
use actix_web::Result as AwResult;
use actix_web::{HttpRequest, delete, get, post, web};
use maud::html;
use serde::Deserialize;

use crate::ConnectionStore;

/// Render the full connections list (used both as a full page and as HTMX partial).
fn connections_list(store: &ConnectionStore) -> maud::Markup {
    let connections = store.all_meta();
    let active_id = store.active_id();

    html! {
        div id="connections-list" class="w-100" {
            @if connections.is_empty() {
                p class="white-60 f6 i tc pv4" { "No connections saved yet. Add one below." }
            } @else {
                @for meta in &connections {
                    @let is_active = active_id.as_deref() == Some(&meta.id);
                    @let has_password = store.has_password(&meta.id);
                    div class={"flex flex-column pa3 br2 mb2 ba "
                        @if is_active { "b--orange bg-orange-10" } @else { "b--white-20 bg-near-black" }}
                        id={"conn-" (meta.id)}
                    {
                        div class="flex items-center justify-between" {
                            div class="flex-auto" {
                                div class="flex items-center mb1" {
                                    span class="f5 fw6 white-90 mr2" { (meta.name) }
                                    @if is_active {
                                        span class="f7 bg-orange white br2 ph2 pv1 fw6 mr2" { "ACTIVE" }
                                    }
                                    @if !has_password {
                                        span class="f7 bg-dark-red white br2 ph2 pv1 fw6" { "NEEDS PASSWORD" }
                                    }
                                }
                                span class="f6 white-50 code" {
                                    (meta.user) "@" (meta.host) ":" (meta.port)
                                }
                            }
                            div class="flex items-center" {
                                @if !is_active {
                                    @if has_password {
                                        button
                                            class="bn bg-transparent white-70 hover-white pointer f6 underline mr3"
                                            hx-post={"/connections/" (meta.id) "/activate"}
                                            hx-target="#connections-list"
                                            hx-swap="outerHTML"
                                            { "Set active" }
                                    }
                                }
                                button
                                    class="bn bg-transparent white-50 hover-white pointer f6"
                                    title="Delete connection"
                                    hx-delete={"/connections/" (meta.id)}
                                    hx-target={"#conn-" (meta.id)}
                                    hx-swap="outerHTML"
                                    hx-confirm="Delete this connection?"
                                    { "✕" }
                            }
                        }
                        // Password input section (shown when no password in memory)
                        @if !has_password {
                            div class="mt3 pt3 bt b--white-10" {
                                form
                                    hx-post={"/connections/" (meta.id) "/password"}
                                    hx-target="#connections-list"
                                    hx-swap="outerHTML"
                                    class="flex items-center"
                                    {
                                    input
                                        type="password"
                                        name="password"
                                        placeholder="Enter password..."
                                        required
                                        class="input-reset ba b--white-30 pa2 br2 f6 bg-black white flex-auto mr2";
                                    button
                                        type="submit"
                                        class="bn bg-orange white br2 ph3 pv2 pointer hover-bg-dark-orange f6 fw6"
                                        { "Set Password" }
                                }
                                p class="f7 white-50 mt2 mb0" {
                                    "Passwords are stored in memory only and will be lost when the server restarts."
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

#[get("/connections")]
pub async fn connections_page(
    req: HttpRequest,
    store: web::Data<ConnectionStore>,
) -> AwResult<maud::Markup> {
    let content = html! {
        div class="w-100 flex flex-column" style="height: 100%;" {
            div class="ph3 mt3" {
                h1 class="f3 fw6 white-90 mb1 lh-title" { "Database Connections" }
                p class="f6 white-50 mt0 mb4" {
                    "Manage your ClickHouse connections. The active connection is used for all queries."
                }

                // Connection list
                (connections_list(&store))

                // Add new connection form
                div class="mt4 bg-near-black ba b--white-20 br2 pa3" {
                    h2 class="f5 fw6 white-90 mt0 mb3" { "Add connection" }
                    form
                        hx-post="/connections"
                        hx-target="#connections-list"
                        hx-swap="outerHTML"
                        hx-on--after-request="this.reset()"
                        class="flex flex-column" {

                        div class="flex flex-wrap nl2 nr2 mb3" {
                            div class="pa2 w-50-ns w-100" {
                                label class="db f6 fw6 white-70 mb1" for="conn-name" { "Name" }
                                input
                                    id="conn-name"
                                    name="name"
                                    type="text"
                                    placeholder="My ClickHouse"
                                    required
                                    class="input-reset ba b--white-30 pa2 w-100 br2 f6 bg-black white";
                            }
                            div class="pa2 w-50-ns w-100" {
                                label class="db f6 fw6 white-70 mb1" for="conn-host" { "Host" }
                                input
                                    id="conn-host"
                                    name="host"
                                    type="text"
                                    placeholder="hostname only, e.g. localhost"
                                    value="localhost"
                                    required
                                    class="input-reset ba b--white-30 pa2 w-100 br2 f6 bg-black white";
                                span class="f7 white-40 mt1 db" { "Hostname only — no http:// or trailing slash. Use port 8443 for TLS." }
                            }
                            div class="pa2 w-25-ns w-50" {
                                label class="db f6 fw6 white-70 mb1" for="conn-port" { "Port" }
                                input
                                    id="conn-port"
                                    name="port"
                                    type="number"
                                    placeholder="8123"
                                    value="8123"
                                    min="1"
                                    max="65535"
                                    required
                                    class="input-reset ba b--white-30 pa2 w-100 br2 f6 bg-black white";
                                span class="f7 white-40 mt1 db" { "8123 = HTTP · 8443 = HTTPS/TLS" }
                            }
                            div class="pa2 w-25-ns w-50" {
                                label class="db f6 fw6 white-70 mb1" for="conn-user" { "User" }
                                input
                                    id="conn-user"
                                    name="user"
                                    type="text"
                                    placeholder="default"
                                    value="default"
                                    required
                                    class="input-reset ba b--white-30 pa2 w-100 br2 f6 bg-black white";
                            }
                            div class="pa2 w-50-ns w-100" {
                                label class="db f6 fw6 white-70 mb1" for="conn-password" { "Password" }
                                input
                                    id="conn-password"
                                    name="password"
                                    type="password"
                                    placeholder="(leave blank if none)"
                                    class="input-reset ba b--white-30 pa2 w-100 br2 f6 bg-black white";
                            }
                        }

                        button
                            type="submit"
                            class="self-start bn bg-orange white br2 ph3 pv2 pointer hover-bg-dark-orange f6 fw6" {
                            "Add connection"
                        }
                    }
                }
            }
        }
    };

    if req.headers().get("HX-Request").is_some() {
        Ok(content)
    } else {
        Ok(super::render_layout(&content))
    }
}

#[derive(Deserialize)]
pub struct NewConnectionForm {
    name: String,
    host: String,
    port: u16,
    user: String,
    #[serde(default)]
    password: String,
}

#[post("/connections")]
pub async fn add_connection(
    form: web::Form<NewConnectionForm>,
    store: web::Data<ConnectionStore>,
) -> AwResult<maud::Markup> {
    store.add(
        form.name.clone(),
        form.host.clone(),
        form.port,
        form.user.clone(),
        form.password.clone(),
    );
    Ok(connections_list(&store))
}

#[delete("/connections/{id}")]
pub async fn delete_connection(
    path: web::Path<String>,
    store: web::Data<ConnectionStore>,
) -> AwResult<maud::Markup> {
    let id = path.into_inner();
    store.remove(&id);
    // Return empty markup — HTMX swaps the deleted row's outerHTML with nothing
    Ok(html! {})
}

#[post("/connections/{id}/activate")]
pub async fn set_active_connection(
    path: web::Path<String>,
    store: web::Data<ConnectionStore>,
) -> AwResult<maud::Markup> {
    let id = path.into_inner();
    store.set_active(&id);
    Ok(connections_list(&store))
}

#[derive(Deserialize)]
pub struct PasswordForm {
    password: String,
}

#[post("/connections/{id}/password")]
pub async fn set_connection_password(
    path: web::Path<String>,
    form: web::Form<PasswordForm>,
    store: web::Data<ConnectionStore>,
) -> AwResult<maud::Markup> {
    let id = path.into_inner();
    store.set_password(&id, form.password.clone());
    log::info!("Password updated for connection {}", id);
    Ok(connections_list(&store))
}