use std::fs;
use std::path::Path;

pub fn generate(name: &str, output: &Path) -> Result<(), String> {
    let crate_name = sanitize_name(name);
    let dir = output.join(&crate_name);

    if dir.exists() {
        return Err(format!("directory already exists: {}", dir.display()));
    }

    let src_dir = dir.join("src");
    fs::create_dir_all(&src_dir).map_err(|e| format!("create dir: {e}"))?;

    // Cargo.toml
    let cargo_toml = format!(
        r#"[package]
name        = "{crate_name}"
version     = "0.1.0"
edition     = "2021"
description = "quicproquo server plugin: {name}"
license     = "MIT"

[lib]
crate-type = ["cdylib"]

# Empty workspace — this plugin builds independently of the qpq workspace.
[workspace]

[dependencies]
quicproquo-plugin-api = {{ git = "https://github.com/nickvidal/quicproquo", default-features = false }}
"#,
        crate_name = crate_name,
        name = name,
    );
    write_file(&dir.join("Cargo.toml"), &cargo_toml)?;

    // src/lib.rs
    let lib_rs = format!(
        r#"//! quicproquo server plugin: {name}
//!
//! Build with: cargo build --release
//! Install:    cp target/release/lib{crate_name}.so /path/to/plugins/
//! The server loads it automatically when started with --plugin-dir.

use quicproquo_plugin_api::{{HookVTable, CMessageEvent, HOOK_CONTINUE, HOOK_REJECT, PLUGIN_OK}};
use std::ffi::CString;
use std::os::raw::c_int;

/// Plugin state — allocate on the heap in init, free in destroy.
struct PluginState {{
    /// Example: maximum allowed payload size in bytes.
    max_payload_bytes: usize,
    /// Stored rejection message (must outlive the hook call).
    reject_msg: Option<CString>,
}}

/// Called by the server on plugin load.
///
/// Fill the vtable with your hook implementations. Return PLUGIN_OK on success.
#[no_mangle]
pub extern "C" fn qpq_plugin_init(vtable: *mut HookVTable) -> c_int {{
    let state = Box::new(PluginState {{
        max_payload_bytes: 1_000_000, // 1 MB limit
        reject_msg: None,
    }});

    let vt = unsafe {{ &mut *vtable }};
    vt.user_data = Box::into_raw(state) as *mut _;
    vt.on_message_enqueue = Some(on_message_enqueue);
    vt.error_message = Some(error_message);
    vt.destroy = Some(destroy);

    eprintln!("[{name}] plugin loaded");
    PLUGIN_OK
}}

/// Hook: called before each message is stored in the delivery queue.
///
/// Return HOOK_CONTINUE to allow, HOOK_REJECT to block.
extern "C" fn on_message_enqueue(
    user_data: *mut std::ffi::c_void,
    event: *const CMessageEvent,
) -> c_int {{
    let state = unsafe {{ &mut *(user_data as *mut PluginState) }};
    let event = unsafe {{ &*event }};

    if event.payload_len > state.max_payload_bytes {{
        let msg = format!(
            "payload too large: {{}} > {{}} bytes",
            event.payload_len, state.max_payload_bytes
        );
        state.reject_msg = CString::new(msg).ok();
        return HOOK_REJECT;
    }}

    HOOK_CONTINUE
}}

/// Return a pointer to the rejection error message (valid until next hook call).
extern "C" fn error_message(
    user_data: *mut std::ffi::c_void,
) -> *const std::os::raw::c_char {{
    let state = unsafe {{ &*(user_data as *const PluginState) }};
    match &state.reject_msg {{
        Some(msg) => msg.as_ptr(),
        None => std::ptr::null(),
    }}
}}

/// Cleanup: free the plugin state.
extern "C" fn destroy(user_data: *mut std::ffi::c_void) {{
    if !user_data.is_null() {{
        unsafe {{ drop(Box::from_raw(user_data as *mut PluginState)) }};
    }}
    eprintln!("[{name}] plugin unloaded");
}}
"#,
        name = name,
        crate_name = crate_name,
    );
    write_file(&src_dir.join("lib.rs"), &lib_rs)?;

    // README
    let readme = format!(
        r#"# {name} — quicproquo server plugin

## Build

```bash
cargo build --release
```

## Install

Copy the shared library to the server's plugin directory:

```bash
cp target/release/lib{crate_name}.so /path/to/plugins/
```

Start the server with:

```bash
qpq-server --plugin-dir /path/to/plugins/
```

## Hooks

This plugin implements `on_message_enqueue` to reject oversized payloads.
Edit `src/lib.rs` to add your own logic. Available hooks:

| Hook | Purpose |
|------|---------|
| `on_message_enqueue` | Inspect/reject messages before delivery (return `HOOK_REJECT`) |
| `on_batch_enqueue` | Observe batch message delivery |
| `on_auth` | Observe login success/failure |
| `on_channel_created` | Observe channel creation |
| `on_fetch` | Observe message fetch operations |
| `on_user_registered` | Observe new user registration |

See the [Server Hooks documentation](https://github.com/nickvidal/quicproquo/blob/main/docs/src/internals/server-hooks.md) for details.
"#,
        name = name,
        crate_name = crate_name,
    );
    write_file(&dir.join("README.md"), &readme)?;

    println!("Created plugin project: {}", dir.display());
    println!();
    println!("  cd {crate_name}");
    println!("  cargo build --release");
    println!("  cp target/release/lib{crate_name}.so /path/to/plugins/");
    println!();
    println!("Edit src/lib.rs to implement your hook logic.");

    Ok(())
}

fn sanitize_name(name: &str) -> String {
    name.replace(['-', ' '], "_")
}

fn write_file(path: &Path, content: &str) -> Result<(), String> {
    fs::write(path, content).map_err(|e| format!("write {}: {e}", path.display()))
}
