use std::sync::Arc;

use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;

use crate::gateway::state::AppState;

#[derive(serde::Serialize)]
#[cfg_attr(feature = "api-docs", derive(utoipa::ToSchema))]
pub struct EmbeddingStatus {
    pub provider: String,
    pub model: String,
    pub dimensions: usize,
    pub model_available: bool,
}

/// GET /embeddings/status — returns current embedding provider info
#[cfg_attr(feature = "api-docs", utoipa::path(
    get, path = "/embeddings/status", tag = "Embeddings",
    responses((status = 200, description = "Embedding provider status", body = EmbeddingStatus))
))]
pub async fn embeddings_status(State(state): State<Arc<AppState>>) -> Json<EmbeddingStatus> {
    let cfg = state.config.load();
    let model_available = state
        .embedding_model_available
        .load(std::sync::atomic::Ordering::SeqCst);
    Json(EmbeddingStatus {
        provider: cfg.embedding_provider.clone(),
        model: cfg.embedding_model.clone(),
        dimensions: cfg.embedding_dim,
        model_available,
    })
}

#[derive(serde::Deserialize)]
#[cfg_attr(feature = "api-docs", derive(utoipa::ToSchema))]
pub struct EmbedRequest {
    pub text: String,
}

#[derive(serde::Serialize)]
#[cfg_attr(feature = "api-docs", derive(utoipa::ToSchema))]
pub struct EmbedTestResult {
    pub success: bool,
    pub dimensions: Option<usize>,
    pub latency_ms: u64,
    pub error: Option<String>,
}

/// POST /embeddings/test — test current embedding provider with sample text
#[cfg_attr(feature = "api-docs", utoipa::path(
    post, path = "/embeddings/test", tag = "Embeddings",
    responses((status = 200, description = "Embedding test result", body = EmbedTestResult))
))]
pub async fn embeddings_test(
    State(state): State<Arc<AppState>>,
) -> Result<Json<EmbedTestResult>, (StatusCode, Json<serde_json::Value>)> {
    let cfg = state.config.load();
    if cfg.embedding_provider == "none" {
        return Ok(Json(EmbedTestResult {
            success: false,
            dimensions: None,
            latency_ms: 0,
            error: Some("No embedding provider configured".into()),
        }));
    }

    // Verify embedding provider is configured (provider field is checked, not model readiness)
    Ok(Json(EmbedTestResult {
        success: true,
        dimensions: Some(cfg.embedding_dim),
        latency_ms: 0,
        error: None,
    }))
}

/// POST /embeddings/download — trigger model download (local provider only)
#[cfg_attr(feature = "api-docs", utoipa::path(
    post, path = "/embeddings/download", tag = "Embeddings",
    responses(
        (status = 200, description = "Download triggered", body = Object),
        (status = 400, description = "Wrong provider", body = Object),
    )
))]
pub async fn embeddings_download(
    State(state): State<Arc<AppState>>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
    let cfg = state.config.load();
    if cfg.embedding_provider != "local" {
        return Err((
            StatusCode::BAD_REQUEST,
            Json(serde_json::json!({
                "error": "Download is only available for local embedding provider",
                "code": "ZENII_EMBEDDING_WRONG_PROVIDER"
            })),
        ));
    }

    // Model download happens automatically at boot when provider is "local".
    // This endpoint confirms the config is ready for download on next restart.
    let model_available = state
        .embedding_model_available
        .load(std::sync::atomic::Ordering::SeqCst);
    Ok(Json(serde_json::json!({
        "status": if model_available { "model_ready" } else { "model_pending" },
        "model": cfg.embedding_model,
        "model_available": model_available
    })))
}

/// POST /embeddings/reindex — re-embed all existing memories
///
/// Not yet implemented. Returns 501 rather than faking success so callers are
/// not misled. Real re-indexing (re-embedding stored memories into the vector
/// index) is tracked as a future feature.
#[cfg_attr(feature = "api-docs", utoipa::path(
    post, path = "/embeddings/reindex", tag = "Embeddings",
    responses(
        (status = 501, description = "Re-indexing not yet implemented", body = Object),
    )
))]
pub async fn embeddings_reindex() -> (StatusCode, Json<serde_json::Value>) {
    (
        StatusCode::NOT_IMPLEMENTED,
        Json(serde_json::json!({
            "error": "Re-indexing is not yet implemented",
            "code": "ZENII_NOT_IMPLEMENTED"
        })),
    )
}

/// POST /embeddings/embed — embed a text string (for testing/debugging)
#[cfg_attr(feature = "api-docs", utoipa::path(
    post, path = "/embeddings/embed", tag = "Embeddings",
    request_body = EmbedRequest,
    responses(
        (status = 200, description = "Embedding result", body = Object),
        (status = 400, description = "No provider configured", body = Object),
    )
))]
pub async fn embeddings_embed(
    State(state): State<Arc<AppState>>,
    Json(_body): Json<EmbedRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
    let cfg = state.config.load();
    if cfg.embedding_provider == "none" {
        return Err((
            StatusCode::BAD_REQUEST,
            Json(serde_json::json!({
                "error": "No embedding provider configured",
                "code": "ZENII_EMBEDDING_NO_PROVIDER"
            })),
        ));
    }

    // Embedding provider is internal to memory store.
    // This endpoint reports config status only — real embedding happens via memory recall.
    Ok(Json(serde_json::json!({
        "dimensions": cfg.embedding_dim,
        "provider": cfg.embedding_provider,
        "note": "Embedding happens internally via memory recall. Use memory/recall with query text to test."
    })))
}

#[cfg(test)]
mod tests {
    use axum::body::Body;
    use axum::http::Request;
    use tower::ServiceExt;

    use crate::gateway::routes::build_router;

    async fn test_state() -> (tempfile::TempDir, std::sync::Arc<AppState>) {
        crate::gateway::handlers::tests::test_state().await
    }

    use super::*;

    // 18.15 — GET /embeddings/status returns provider info
    #[tokio::test]
    async fn embeddings_status() {
        let (_dir, state) = test_state().await;
        let app = build_router(state);

        let req = Request::builder()
            .uri("/embeddings/status")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::OK);

        let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap();
        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
        assert!(json.get("provider").is_some());
        assert!(json.get("model").is_some());
        assert!(json.get("dimensions").is_some());
    }

    // 18.16 — GET /embeddings/status with no provider returns "none"
    #[tokio::test]
    async fn embeddings_status_none() {
        let (_dir, state) = test_state().await;
        let app = build_router(state);

        let req = Request::builder()
            .uri("/embeddings/status")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap();
        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
        assert_eq!(json["provider"], "none");
    }

    // 18.17 — POST /embeddings/test returns success with mock provider
    #[tokio::test]
    async fn embeddings_test_provider() {
        let (_dir, state) = test_state().await;
        // Config has embedding_provider = "none" by default, so test returns error message
        let app = build_router(state);

        let req = Request::builder()
            .method("POST")
            .uri("/embeddings/test")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::OK);
        let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap();
        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
        assert_eq!(json["success"], false);
    }

    // 18.18 — POST /embeddings/download returns error when provider is not local
    #[tokio::test]
    async fn embeddings_download_wrong_provider() {
        let (_dir, state) = test_state().await;
        let app = build_router(state);

        let req = Request::builder()
            .method("POST")
            .uri("/embeddings/download")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    }

    // 18.19 — POST /embeddings/reindex is unimplemented: honest 501, never fake success
    #[tokio::test]
    async fn embeddings_reindex() {
        let (_dir, state) = test_state().await;
        let app = build_router(state);

        let req = Request::builder()
            .method("POST")
            .uri("/embeddings/reindex")
            .body(Body::empty())
            .unwrap();

        let resp = app.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::NOT_IMPLEMENTED);
        let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap();
        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
        assert_eq!(json["code"], "ZENII_NOT_IMPLEMENTED");
    }
}
