use axum::{extract::State, http::Method, routing::post, Router};
use clap::Parser;
use serde::Deserialize;
use std::path::PathBuf;
use std::sync::Arc;
use tower_http::cors::{Any, CorsLayer};
use tower_http::services::ServeDir;

#[derive(Parser)]
#[command(name = "noter")]
struct Args {
    #[arg(short, long, default_value_t = 9876)]
    port: u16,
}

#[derive(Deserialize)]
struct NotePayload {
    fid: String,
    title: String,
    md: String,
}

#[derive(Clone)]
struct AppState {
    save_dir: PathBuf,
}

fn sanitize_filename(s: &str) -> String {
    s.chars()
        .map(|c| match c {
            ' ' => '-',
            '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
            _ => c,
        })
        .collect()
}

async fn save_note(
    State(state): State<Arc<AppState>>,
    axum::Json(payload): axum::Json<NotePayload>,
) -> axum::Json<serde_json::Value> {
    let filename = format!("{}_{}.md", payload.fid, sanitize_filename(&payload.title));
    let filepath = state.save_dir.join(&filename);

    match std::fs::write(&filepath, &payload.md) {
        Ok(_) => axum::Json(serde_json::json!({
            "ok": true,
            "path": filepath.to_string_lossy()
        })),
        Err(e) => axum::Json(serde_json::json!({
            "ok": false,
            "error": e.to_string()
        })),
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Args::parse();

    let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .to_path_buf();

    let save_dir = home_dir().join(".noter").join("mds");
    std::fs::create_dir_all(&save_dir)?;
    println!("Save directory: {}", save_dir.display());

    let state = Arc::new(AppState { save_dir });

    let cors = CorsLayer::new()
        .allow_origin(Any)
        .allow_methods([Method::POST]);

    let app = Router::new()
        .route("/api/note/save", post(save_note))
        .fallback_service(ServeDir::new(&project_root).append_index_html_on_directories(true))
        .layer(cors)
        .with_state(state);

    let addr = format!("0.0.0.0:{}", args.port);
    println!("Noter server running at http://localhost:{}", args.port);

    let listener = tokio::net::TcpListener::bind(&addr).await?;
    axum::serve(listener, app).await?;

    Ok(())
}

fn home_dir() -> PathBuf {
    if let Ok(home) = std::env::var("HOME") {
        return PathBuf::from(home);
    }
    if let Ok(profile) = std::env::var("USERPROFILE") {
        return PathBuf::from(profile);
    }
    let drive = std::env::var("HOMEDRIVE").unwrap_or_default();
    let path = std::env::var("HOMEPATH").unwrap_or_default();
    let home = format!("{}{}", drive, path);
    if !home.is_empty() {
        return PathBuf::from(home);
    }
    PathBuf::from(".")
}
