//! Strategies page — list / create / edit-config / delete. Assignment to
//! peers/groups/users is intentionally still SQL-driven for v1; building a
//! full assignment matrix UI is a follow-up.

use super::shared::{html_escape, notice_html, require_admin};
use crate::api::error::ApiError;
use crate::api::middleware::AuthedUser;
use crate::api::state::AppState;
use axum::extract::{Extension, Form, Path};
use axum::response::Html;
use serde::Deserialize;
use std::fmt::Write as _;
use std::sync::Arc;

pub async fn index(
    Extension(state): Extension<Arc<AppState>>,
    admin: AuthedUser,
) -> Result<Html<String>, ApiError> {
    require_admin(&admin)?;
    Ok(Html(render_full(&state).await?))
}

#[derive(Debug, Deserialize)]
pub struct CreateForm {
    pub name: String,
    #[serde(default)]
    pub config_options_json: String,
}

pub async fn create(
    Extension(state): Extension<Arc<AppState>>,
    admin: AuthedUser,
    Form(form): Form<CreateForm>,
) -> Result<Html<String>, ApiError> {
    require_admin(&admin)?;
    if form.name.trim().is_empty() {
        return notice_then(&state, "error", "Name required").await;
    }
    let cfg = if form.config_options_json.trim().is_empty() {
        "{}".to_string()
    } else {
        // Validate it's a JSON object — empty object is fine, anything else
        // gets rejected with a friendly message.
        match serde_json::from_str::<serde_json::Value>(&form.config_options_json) {
            Ok(v) if v.is_object() => form.config_options_json.clone(),
            Ok(_) => {
                return notice_then(&state, "error", "config_options must be a JSON object").await
            }
            Err(e) => return notice_then(&state, "error", &format!("invalid JSON: {}", e)).await,
        }
    };
    state
        .db
        .strategy_create(form.name.trim(), &cfg, "{}")
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    notice_then(
        &state,
        "ok",
        &format!("Strategy '{}' created.", form.name),
    )
    .await
}

#[derive(Debug, Deserialize)]
pub struct UpdateForm {
    pub config_options_json: String,
}

pub async fn update(
    Extension(state): Extension<Arc<AppState>>,
    admin: AuthedUser,
    Path(id): Path<i64>,
    Form(form): Form<UpdateForm>,
) -> Result<Html<String>, ApiError> {
    require_admin(&admin)?;
    let cfg = match serde_json::from_str::<serde_json::Value>(&form.config_options_json) {
        Ok(v) if v.is_object() => form.config_options_json.clone(),
        _ => {
            return notice_then(&state, "error", "config_options must be a JSON object").await
        }
    };
    state
        .db
        .strategy_update_config(id, &cfg)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    notice_then(&state, "ok", "Strategy updated.").await
}

pub async fn delete(
    Extension(state): Extension<Arc<AppState>>,
    admin: AuthedUser,
    Path(id): Path<i64>,
) -> Result<Html<String>, ApiError> {
    require_admin(&admin)?;
    let ok = state
        .db
        .strategy_delete(id)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    notice_then(
        &state,
        if ok { "ok" } else { "error" },
        if ok { "Strategy deleted." } else { "Already gone." },
    )
    .await
}

// ---------- rendering ----------

async fn notice_then(
    state: &Arc<AppState>,
    kind: &str,
    msg: &str,
) -> Result<Html<String>, ApiError> {
    let mut html = notice_html(kind, msg);
    html.push_str(&render_full(state).await?);
    Ok(Html(html))
}

async fn render_full(state: &Arc<AppState>) -> Result<String, ApiError> {
    let strategies = state
        .db
        .strategies_list_all()
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    let mut s = String::new();
    s.push_str(
        r##"<div id="strategies-region" class="space-y-6">
  <header>
    <h2 class="text-lg font-semibold">Strategies</h2>
    <p class="text-xs text-slate-500 mt-1">Pushed to clients via heartbeat. Use SQL to assign — strategy_assignments(strategy_id, user_id|device_group_id|peer_id, priority).</p>
  </header>
  <section class="rounded-md border border-slate-800 bg-slate-900 p-4">
    <h3 class="text-sm font-semibold text-slate-300 mb-3">Create strategy</h3>
    <form class="space-y-2 text-sm" hx-post="/admin/pages/strategies/create" hx-target="#strategies-region" hx-swap="outerHTML">
      <input name="name" placeholder="name (unique)" required class="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5"/>
      <textarea name="config_options_json" rows="3" placeholder='{"enable-udp": "N", "whitelist": ""}'
        class="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 font-mono text-xs"></textarea>
      <button class="bg-sky-600 hover:bg-sky-500 rounded px-3 py-1.5 font-medium text-white">Create</button>
    </form>
  </section>
"##,
    );
    if strategies.is_empty() {
        s.push_str(r##"<p class="text-slate-500 text-sm">No strategies yet.</p>"##);
    }
    for str_ in &strategies {
        let _ = write!(
            s,
            r##"<section class="rounded-md border border-slate-800 bg-slate-900 p-4 space-y-3">
  <header class="flex items-center justify-between">
    <div>
      <h3 class="font-semibold">{name}</h3>
      <p class="text-xs text-slate-500">id={id}, modified_at={mod_at}</p>
    </div>
    <button class="text-xs text-rose-400 hover:text-rose-300"
      hx-post="/admin/pages/strategies/{id}/delete"
      hx-confirm="Delete strategy {name}? Assignments will be cleaned up too."
      hx-target="#strategies-region" hx-swap="outerHTML">Delete</button>
  </header>
  <form class="space-y-2 text-sm"
    hx-post="/admin/pages/strategies/{id}/update"
    hx-target="#strategies-region" hx-swap="outerHTML">
    <label class="block text-xs text-slate-400">config_options (JSON object)</label>
    <textarea name="config_options_json" rows="4"
      class="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 font-mono text-xs">{cfg}</textarea>
    <button class="bg-sky-700 hover:bg-sky-600 rounded px-3 py-1.5 text-xs">Save</button>
  </form>
</section>"##,
            id = str_.id,
            name = html_escape(&str_.name),
            mod_at = str_.modified_at,
            cfg = html_escape(&str_.config_options_json),
        );
    }
    s.push_str("</div>");
    Ok(s)
}
