use std::sync::Arc;

use axum::extract::{Path, State};
use axum::response::Html;
use serde::Deserialize;
use tera::Context;

use super::super::auth::AuthUser;
use super::common::{auth_role_str, insert_brand_ctx, render, tera_ref, UiError};
use crate::api::AppState;

fn default_providers() -> Vec<serde_json::Value> {
    vec![
        serde_json::json!({ "id": "anthropic", "label": "Anthropic (Claude)", "api_url": "", "model": "claude-opus-4-6", "key_env": "ANTHROPIC_API_KEY", "kind": "anthropic" }),
        serde_json::json!({ "id": "openai",    "label": "OpenAI (GPT-4o)",     "api_url": "", "model": "gpt-4o",           "key_env": "OPENAI_API_KEY",     "kind": "openai"    }),
        serde_json::json!({ "id": "local",     "label": "Local (Ollama)",       "api_url": "http://localhost:11434/v1/chat/completions", "model": "llama3.2", "key_env": "", "kind": "openai" }),
    ]
}

async fn read_providers(
    root: &std::path::Path,
    cache: &Arc<crate::cache::NclCache>,
    import_path: Option<&str>,
) -> Vec<serde_json::Value> {
    let config_path = root.join(".ontoref").join("config.ncl");
    if config_path.exists() {
        if let Ok((json, _)) = cache.export(&config_path, import_path).await {
            if let Some(arr) = json.get("providers").and_then(|v| v.as_array()) {
                if !arr.is_empty() {
                    return arr.clone();
                }
            }
        }
    }
    default_providers()
}

async fn call_anthropic_api(
    url: &str,
    key: &str,
    model: &str,
    system: &str,
    prompt: &str,
) -> anyhow::Result<serde_json::Value> {
    let effective_url = if url.is_empty() {
        "https://api.anthropic.com/v1/messages"
    } else {
        url
    };
    let mut req = serde_json::json!({
        "model": model,
        "max_tokens": 4096,
        "messages": [{"role": "user", "content": prompt}]
    });
    if !system.is_empty() {
        req["system"] = serde_json::Value::String(system.to_string());
    }
    let resp = reqwest::Client::new()
        .post(effective_url)
        .header("x-api-key", key)
        .header("anthropic-version", "2023-06-01")
        .json(&req)
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;
    Ok(resp)
}

async fn call_openai_api(
    url: &str,
    key: &str,
    model: &str,
    system: &str,
    prompt: &str,
) -> anyhow::Result<serde_json::Value> {
    let effective_url = if url.is_empty() {
        "https://api.openai.com/v1/chat/completions"
    } else {
        url
    };
    let mut messages: Vec<serde_json::Value> = Vec::new();
    if !system.is_empty() {
        messages.push(serde_json::json!({"role": "system", "content": system}));
    }
    messages.push(serde_json::json!({"role": "user", "content": prompt}));
    let resp = reqwest::Client::new()
        .post(effective_url)
        .header("Authorization", format!("Bearer {key}"))
        .json(&serde_json::json!({ "model": model, "messages": messages }))
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;
    Ok(resp)
}

pub async fn compose_page_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    auth: AuthUser,
) -> Result<Html<String>, UiError> {
    let tera = tera_ref(&state)?;
    let ctx_ref = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;

    let forms_dir = ontoref_ontology::layout::resolve_section(&ctx_ref.root, "reflection/forms");
    let mut forms: Vec<serde_json::Value> = Vec::new();
    if let Ok(entries) = std::fs::read_dir(&forms_dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.extension().and_then(|e| e.to_str()) != Some("ncl") {
                continue;
            }
            let stem = path
                .file_stem()
                .and_then(|s| s.to_str())
                .unwrap_or("")
                .to_string();
            forms.push(serde_json::json!({
                "id":    stem,
                "label": stem.replace('_', " "),
            }));
        }
    }
    forms.sort_by_key(|v| {
        v.get("id")
            .and_then(|s| s.as_str())
            .unwrap_or("")
            .to_string()
    });

    let providers = read_providers(
        &ctx_ref.root,
        &ctx_ref.cache,
        ctx_ref.import_path.as_deref(),
    )
    .await;

    let base_url = format!("/ui/{slug}");
    let mut ctx = Context::new();
    ctx.insert("forms", &forms);
    ctx.insert("providers", &providers);
    ctx.insert("base_url", &base_url);
    ctx.insert("slug", &slug);
    ctx.insert("current_role", &auth_role_str(&auth));
    insert_brand_ctx(
        &mut ctx,
        &ctx_ref.root,
        &ctx_ref.cache,
        ctx_ref.import_path.as_deref(),
        &base_url,
        &state.registry,
        &state.service_flags,
    )
    .await;

    render(tera, "pages/compose.html", &ctx).await
}

pub async fn compose_form_schema_mp(
    State(state): State<AppState>,
    Path((slug, form_id)): Path<(String, String)>,
    _auth: AuthUser,
) -> Result<axum::Json<serde_json::Value>, UiError> {
    let ctx_ref = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;

    let form_path = ontoref_ontology::layout::resolve_section(
        &ctx_ref.root,
        &format!("reflection/forms/{form_id}.ncl"),
    );

    if !form_path.exists() {
        return Err(UiError::BadRequest(format!("form not found: {form_id}")));
    }

    let (json, _) = ctx_ref
        .cache
        .export(&form_path, ctx_ref.import_path.as_deref())
        .await
        .map_err(|e| UiError::NclExport {
            path: form_id.clone(),
            reason: e.to_string(),
        })?;

    Ok(axum::Json(json))
}

#[derive(Deserialize)]
pub struct ComposeSendBody {
    pub provider_id: String,
    pub prompt: String,
    #[serde(default)]
    pub system: String,
}

pub async fn compose_send_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    _auth: AuthUser,
    axum::Json(body): axum::Json<ComposeSendBody>,
) -> Result<axum::Json<serde_json::Value>, UiError> {
    let ctx_ref = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;

    let providers = read_providers(
        &ctx_ref.root,
        &ctx_ref.cache,
        ctx_ref.import_path.as_deref(),
    )
    .await;

    let provider = providers
        .iter()
        .find(|p| p.get("id").and_then(|v| v.as_str()) == Some(body.provider_id.as_str()))
        .ok_or_else(|| UiError::BadRequest(format!("unknown provider: {}", body.provider_id)))?
        .clone();

    let api_url = provider
        .get("api_url")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let model = provider
        .get("model")
        .and_then(|v| v.as_str())
        .unwrap_or("claude-opus-4-6")
        .to_string();
    let key_env = provider
        .get("key_env")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let kind = provider
        .get("kind")
        .and_then(|v| v.as_str())
        .unwrap_or("anthropic")
        .to_string();

    let api_key = if key_env.is_empty() {
        String::new()
    } else {
        std::env::var(&key_env).unwrap_or_default()
    };

    if api_key.is_empty() && !matches!(kind.as_str(), "openai") {
        return Err(UiError::BadRequest(format!(
            "API key not configured: set {} environment variable",
            key_env
        )));
    }

    let result = match kind.as_str() {
        "openai" => call_openai_api(&api_url, &api_key, &model, &body.system, &body.prompt).await,
        _ => call_anthropic_api(&api_url, &api_key, &model, &body.system, &body.prompt).await,
    };

    result
        .map(axum::Json)
        .map_err(|e| UiError::BadRequest(e.to_string()))
}
