use std::sync::Arc;

use axum::Json;
use axum::extract::{Path, State};
use axum::response::IntoResponse;
use serde::{Deserialize, Serialize};

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

#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "api-docs", derive(utoipa::ToSchema))]
pub struct SetCredentialRequest {
    pub key: String,
    pub value: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "api-docs", derive(utoipa::ToSchema))]
pub struct CredentialExistsResponse {
    pub exists: bool,
}

/// POST /credentials -- set a credential.
#[cfg_attr(feature = "api-docs", utoipa::path(
    post, path = "/credentials", tag = "Credentials",
    request_body = SetCredentialRequest,
    responses((status = 200, description = "Credential stored", body = Object))
))]
pub async fn set_credential(
    State(state): State<Arc<AppState>>,
    Json(req): Json<SetCredentialRequest>,
) -> crate::Result<impl IntoResponse> {
    state.credentials.set(&req.key, &req.value).await?;
    let _ = state
        .event_bus
        .publish(crate::event_bus::AppEvent::CredentialsChanged);
    Ok(Json(serde_json::json!({ "ok": true })))
}

/// GET /credentials -- list stored credential keys (names only, never values).
#[cfg_attr(feature = "api-docs", utoipa::path(
    get, path = "/credentials", tag = "Credentials",
    responses((status = 200, description = "List of credential keys", body = Vec<String>))
))]
pub async fn list_credentials(
    State(state): State<Arc<AppState>>,
) -> crate::Result<impl IntoResponse> {
    let keys = state.credentials.list().await?;
    Ok(Json(keys))
}

/// DELETE /credentials/{key} -- remove a credential.
#[cfg_attr(feature = "api-docs", utoipa::path(
    delete, path = "/credentials/{key}", tag = "Credentials",
    params(("key" = String, Path, description = "Credential key")),
    responses((status = 200, description = "Credential deleted", body = Object))
))]
pub async fn delete_credential(
    State(state): State<Arc<AppState>>,
    Path(key): Path<String>,
) -> crate::Result<impl IntoResponse> {
    let deleted = state.credentials.delete(&key).await?;
    let _ = state
        .event_bus
        .publish(crate::event_bus::AppEvent::CredentialsChanged);
    Ok(Json(serde_json::json!({ "deleted": deleted })))
}

/// GET /credentials/{key}/value -- check if a credential exists (no raw value exposed).
///
/// Returns `{ "exists": true/false }` instead of the raw secret.
/// Raw credential values must never be returned over the gateway.
#[cfg_attr(feature = "api-docs", utoipa::path(
    get, path = "/credentials/{key}/value", tag = "Credentials",
    params(("key" = String, Path, description = "Credential key")),
    responses((status = 200, description = "Credential existence check", body = CredentialExistsResponse))
))]
pub async fn get_credential_value(
    State(state): State<Arc<AppState>>,
    Path(key): Path<String>,
) -> crate::Result<impl IntoResponse> {
    let value = state.credentials.get(&key).await?;
    Ok(Json(CredentialExistsResponse {
        exists: value.is_some(),
    }))
}

/// GET /credentials/{key}/exists -- check if a credential exists (bool, no value).
#[cfg_attr(feature = "api-docs", utoipa::path(
    get, path = "/credentials/{key}/exists", tag = "Credentials",
    params(("key" = String, Path, description = "Credential key")),
    responses((status = 200, description = "Credential existence check", body = CredentialExistsResponse))
))]
pub async fn credential_exists(
    State(state): State<Arc<AppState>>,
    Path(key): Path<String>,
) -> crate::Result<impl IntoResponse> {
    let value = state.credentials.get(&key).await?;
    Ok(Json(CredentialExistsResponse {
        exists: value.is_some(),
    }))
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::Router;
    use axum::body::Body;
    use axum::http::{Request, StatusCode};
    use axum::routing::{delete, get, post};
    use tempfile::TempDir;
    use tower::ServiceExt;

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

    fn app(state: Arc<AppState>) -> Router {
        Router::new()
            .route("/credentials", post(set_credential).get(list_credentials))
            .route("/credentials/{key}", delete(delete_credential))
            .route("/credentials/{key}/value", get(get_credential_value))
            .route("/credentials/{key}/exists", get(credential_exists))
            .with_state(state)
    }

    #[tokio::test]
    async fn set_credential_test() {
        let (_dir, state) = test_state().await;
        let app = app(state);

        let req = Request::builder()
            .method("POST")
            .uri("/credentials")
            .header("content-type", "application/json")
            .body(Body::from(
                serde_json::to_string(&SetCredentialRequest {
                    key: "api_key:openai".into(),
                    value: "sk-test".into(),
                })
                .unwrap(),
            ))
            .unwrap();

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

    #[tokio::test]
    async fn list_credential_keys() {
        let (_dir, state) = test_state().await;
        state
            .credentials
            .set("api_key:openai", "sk-test")
            .await
            .unwrap();

        let app = app(state);
        let req = Request::builder()
            .uri("/credentials")
            .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 keys: Vec<String> = serde_json::from_slice(&body).unwrap();
        assert!(keys.contains(&"api_key:openai".to_string()));
    }

    #[tokio::test]
    async fn delete_credential_test() {
        let (_dir, state) = test_state().await;
        state.credentials.set("api_key:test", "val").await.unwrap();

        let app = app(state);
        let req = Request::builder()
            .method("DELETE")
            .uri("/credentials/api_key: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 result: serde_json::Value = serde_json::from_slice(&body).unwrap();
        assert_eq!(result["deleted"], true);
    }

    // WS-4.2 — /credentials/{key}/value no longer returns raw secret
    #[tokio::test]
    async fn credential_value_endpoint_no_raw_value() {
        let (_dir, state) = test_state().await;
        state
            .credentials
            .set("api_key:openai", "sk-secret-test-value")
            .await
            .unwrap();

        let router = app(state);
        let req = Request::builder()
            .uri("/credentials/api_key:openai/value")
            .body(Body::empty())
            .unwrap();
        let resp = router.oneshot(req).await.unwrap();
        assert_eq!(resp.status(), StatusCode::OK);

        let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap();
        let body_str = String::from_utf8_lossy(&body);

        // Must NOT contain the actual secret value
        assert!(
            !body_str.contains("sk-secret-test-value"),
            "Response must not contain the raw credential value"
        );

        // Should contain exists: true
        let result: CredentialExistsResponse = serde_json::from_slice(&body).unwrap();
        assert!(result.exists);
    }

    #[tokio::test]
    async fn credential_exists_test() {
        let (_dir, state) = test_state().await;
        state
            .credentials
            .set("api_key:openai", "sk-test")
            .await
            .unwrap();

        let router = app(state.clone());

        // Exists
        let req = Request::builder()
            .uri("/credentials/api_key:openai/exists")
            .body(Body::empty())
            .unwrap();
        let resp = router.oneshot(req).await.unwrap();
        let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap();
        let result: CredentialExistsResponse = serde_json::from_slice(&body).unwrap();
        assert!(result.exists);

        // Does not exist
        let router = app(state);
        let req = Request::builder()
            .uri("/credentials/api_key:missing/exists")
            .body(Body::empty())
            .unwrap();
        let resp = router.oneshot(req).await.unwrap();
        let body = axum::body::to_bytes(resp.into_body(), 4096).await.unwrap();
        let result: CredentialExistsResponse = serde_json::from_slice(&body).unwrap();
        assert!(!result.exists);
    }
}
