//! Example tier-2 catalog sidecar — implements the
//! `send_email_notification` op behind HTTP at `http://127.0.0.1:7902`.
//!
//! Wire shape matches the contract the main `ontoref-daemon` will speak
//! per ADR-030 mechanism #6: it POSTs a `DispatchRequest`-shaped JSON
//! body and expects a `DispatchResponse`-shaped JSON body in return.
//! The local types here mirror the daemon's surface without taking a
//! path dependency on `crates/ontoref-ops` so this example stays
//! self-contained.
//!
//! Run with:
//!     cargo run --bin example-mail-sidecar
//!
//! Hit with:
//!     curl -X POST http://127.0.0.1:7902/ops/send_email_notification \
//!       -H "Content-Type: application/json" \
//!       -d '{"inputs":{"recipient":"a@b.c","subject":"hi","body":"hello","template":"plain"}}'

use axum::{
    extract::{Json, Path},
    http::StatusCode,
    response::IntoResponse,
    routing::post,
    Router,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use tracing::{error, info};

#[derive(Deserialize)]
struct DispatchRequest {
    inputs: SendEmailInputs,
}

#[derive(Deserialize)]
struct SendEmailInputs {
    recipient: String,
    subject:   String,
    body:      String,
    template:  String,
}

#[derive(Serialize)]
struct DispatchResponse {
    status:     &'static str,
    message_id: String,
    payload:    SendEmailPayload,
}

#[derive(Serialize)]
struct SendEmailPayload {
    recipient: String,
    subject:   String,
    template:  String,
    bytes:     usize,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "example_mail_sidecar=info".into()),
        )
        .init();

    let app = Router::new().route("/ops/:op_id", post(dispatch));
    let addr: SocketAddr = "127.0.0.1:7902".parse()?;
    info!(%addr, "example-mail-sidecar listening");

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

async fn dispatch(
    Path(op_id): Path<String>,
    Json(req): Json<DispatchRequest>,
) -> impl IntoResponse {
    if op_id != "send_email_notification" {
        return (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({
                "error":  "unknown_op",
                "op_id":  op_id,
                "available": ["send_email_notification"],
            })),
        )
            .into_response();
    }

    match send_email(req.inputs) {
        Ok(resp) => (StatusCode::OK, Json(serde_json::to_value(resp).unwrap())).into_response(),
        Err(err) => {
            error!(?err, "send_email failed");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": err })),
            )
                .into_response()
        }
    }
}

fn send_email(inputs: SendEmailInputs) -> Result<DispatchResponse, String> {
    if !inputs.recipient.contains('@') {
        return Err(format!("recipient '{}' is not an email address", inputs.recipient));
    }
    if inputs.subject.is_empty() {
        return Err("subject must be non-empty".to_string());
    }
    let message_id = format!(
        "msg-{:x}-{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_nanos())
            .unwrap_or(0),
        inputs.recipient.split('@').next().unwrap_or("anon"),
    );
    info!(
        recipient = %inputs.recipient,
        subject   = %inputs.subject,
        template  = %inputs.template,
        message_id,
        "would dispatch email (this example does not actually send)",
    );
    Ok(DispatchResponse {
        status:     "queued",
        message_id,
        payload: SendEmailPayload {
            recipient: inputs.recipient,
            subject:   inputs.subject,
            template:  inputs.template,
            bytes:     inputs.body.len(),
        },
    })
}
