use crate::app::AppConfig;
use crate::context::RequestContext;
use crate::error::WebError;
use bridge_runtime::{RuntimeCommandExecutionPlan, RuntimeQueryExecutionPlan};
use serde_json::{json, Value};
use std::fs;
use std::time::Duration;

const HEADER_REQUEST_ID: &str = "x-request-id";
const HEADER_TRACE_ID: &str = "x-trace-id";
const HEADER_WORKSPACE_ID: &str = "x-mnote-workspace-id";
const HEADER_SOURCE_CHANNEL: &str = "x-mnote-source-channel";
const HEADER_SOURCE_CLIENT: &str = "x-mnote-source-client";
const HEADER_IDEMPOTENCY_KEY: &str = "x-idempotency-key";
const COOKIE_MNOTE_WEB_CONVEX_TOKEN: &str = "mnote_web_convex_token";

fn read_env_or_dotenv(key: &str) -> Option<String> {
    if let Ok(value) = std::env::var(key) {
        let trimmed = value.trim().trim_matches('"').to_string();
        if !trimmed.is_empty() {
            return Some(trimmed);
        }
    }

    let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../..")
        .join(".env.all");
    let content = fs::read_to_string(root).ok()?;
    for line in content.lines() {
        let line = line.trim_end_matches('\r');
        if line.starts_with('#') || line.trim().is_empty() {
            continue;
        }
        let Some((k, v)) = line.split_once('=') else {
            continue;
        };
        if k.trim() != key {
            continue;
        }
        let trimmed = v.trim().trim_matches('"').to_string();
        if !trimmed.is_empty() {
            return Some(trimmed);
        }
    }
    None
}

fn build_authorization(config: &AppConfig, context: &RequestContext) -> Result<String, WebError> {
    if let Some(authorization) = context
        .auth
        .authorization
        .as_deref()
        .map(str::trim)
        .filter(|value| !value.is_empty())
    {
        return Ok(authorization.to_string());
    }

    if let Some(convex_token) = extract_cookie_value(context, COOKIE_MNOTE_WEB_CONVEX_TOKEN) {
        return Ok(format!("Bearer {convex_token}"));
    }

    let admin_key = config
        .convex_admin_key
        .clone()
        .or_else(|| read_env_or_dotenv("CONVEX_SELF_HOSTED_ADMIN_KEY"))
        .ok_or_else(|| {
            WebError::service_unavailable_code(
                "convex_config_missing",
                "缺少 CONVEX_SELF_HOSTED_ADMIN_KEY，且请求未携带 Authorization",
            )
            .with_context(context)
            .with_header("x-error-phase", "convex_auth")
            .with_header("x-upstream-service", "convex")
        })?;

    // 说明：
    // - Next 侧 `setAdminAuth(adminKey)` 走的是纯 admin auth，而不是伪造用户身份。
    // - Convex 业务函数内部会在缺少真实 token 时自行回退到 DEV_USER_ID。
    // - 这里若强行附带伪造 identity，会让 getAuthUserId(ctx) 命中一个未映射用户，
    //   反而绕过 DEV_USER_ID fallback，导致 workspace membership 校验失败。
    Ok(format!("Convex {admin_key}"))
}

fn extract_cookie_value(context: &RequestContext, name: &str) -> Option<String> {
    context
        .auth
        .cookie_header
        .as_deref()
        .and_then(|cookie_header| {
            cookie_header.split(';').find_map(|segment| {
                let (key, value) = segment.trim().split_once('=')?;
                if key.trim() != name {
                    return None;
                }
                let trimmed = value.trim();
                if trimmed.is_empty() {
                    return None;
                }
                Some(trimmed.to_string())
            })
        })
}

fn convex_url(config: &AppConfig, context: &RequestContext) -> Result<String, WebError> {
    config
        .convex_url
        .clone()
        .or_else(|| read_env_or_dotenv("CONVEX_SELF_HOSTED_URL"))
        .or_else(|| read_env_or_dotenv("NEXT_PUBLIC_CONVEX_URL"))
        .map(|value| value.trim().trim_end_matches('/').to_string())
        .filter(|value| !value.is_empty())
        .ok_or_else(|| {
            WebError::service_unavailable_code(
                "convex_config_missing",
                "缺少 CONVEX_SELF_HOSTED_URL / NEXT_PUBLIC_CONVEX_URL",
            )
            .with_context(context)
            .with_header("x-error-phase", "convex_url")
            .with_header("x-upstream-service", "convex")
        })
}

fn load_query_fixture(
    config: &AppConfig,
    context: &RequestContext,
    plan: &RuntimeQueryExecutionPlan,
) -> Result<Option<Value>, WebError> {
    if !config.allow_dev_fixtures {
        return Ok(None);
    }

    let Some(raw) = config.query_fixtures_json.as_deref() else {
        return Ok(None);
    };
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return Ok(None);
    }

    let fixtures: Value = serde_json::from_str(trimmed).map_err(|error| {
        WebError::internal(format!("MNOTE_WEB_QUERY_FIXTURES_JSON 非法: {error}"))
            .with_context(context)
            .with_header("x-error-phase", "fixture_parse")
    })?;

    Ok(fixtures
        .as_object()
        .and_then(|map| map.get(plan.function_name.as_str()))
        .cloned())
}

fn load_mutation_fixture(
    config: &AppConfig,
    context: &RequestContext,
    plan: &RuntimeCommandExecutionPlan,
) -> Result<Option<Value>, WebError> {
    if !config.allow_dev_fixtures {
        return Ok(None);
    }

    let Some(raw) = config.mutation_fixtures_json.as_deref() else {
        return Ok(None);
    };
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return Ok(None);
    }

    let fixtures: Value = serde_json::from_str(trimmed).map_err(|error| {
        WebError::internal(format!("MNOTE_WEB_MUTATION_FIXTURES_JSON 非法: {error}"))
            .with_context(context)
            .with_header("x-error-phase", "fixture_parse")
    })?;

    Ok(fixtures
        .as_object()
        .and_then(|map| map.get(plan.function_name.as_str()))
        .cloned())
}

pub async fn execute_convex_query_plan(
    config: &AppConfig,
    context: &RequestContext,
    plan: &RuntimeQueryExecutionPlan,
) -> Result<Value, WebError> {
    if plan.function_name.trim().is_empty() || plan.function_name.ends_with(":unknown") {
        return Err(WebError::bad_request_code(
            "transport_query_unsupported",
            format!("mnote-web transport 暂不支持 query: {}", plan.function_name),
        )
        .with_context(context)
        .with_header("x-error-phase", "plan_validation"));
    }

    if let Some(fixture) = load_query_fixture(config, context, plan)? {
        return Ok(fixture);
    }

    let payload = json!({
        "path": plan.function_name,
        "format": "convex_encoded_json",
        "args": plan.args_json,
    });

    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(20))
        .build()
        .map_err(|error| {
            WebError::internal(format!("Convex HTTP 客户端创建失败: {error}"))
                .with_context(context)
                .with_header("x-error-phase", "client_build")
                .with_header("x-upstream-service", "convex")
        })?;

    let mut request = client
        .post(format!("{}/api/query", convex_url(config, context)?))
        .header("Authorization", build_authorization(config, context)?)
        .header("Content-Type", "application/json")
        .header("Convex-Client", "mnote-web")
        .header(HEADER_REQUEST_ID, &context.trace.request_id)
        .header(HEADER_TRACE_ID, &context.trace.trace_id)
        .header(HEADER_SOURCE_CHANNEL, context.source.channel.as_str())
        .header(HEADER_SOURCE_CLIENT, context.source.client.as_str())
        .json(&payload);

    if let Some(workspace_id) = plan
        .workspace_id
        .as_deref()
        .or(context.workspace.workspace_id.as_deref())
    {
        request = request.header(HEADER_WORKSPACE_ID, workspace_id);
    }

    let response = request.send().await.map_err(|error| {
        let base = if error.is_timeout() {
            WebError::gateway_timeout_code("convex_timeout", format!("Convex query 超时: {error}"))
        } else {
            WebError::service_unavailable_code(
                "convex_unavailable",
                format!("Convex query 请求失败: {error}"),
            )
        };
        base.with_context(context)
            .with_header("x-error-phase", "query_send")
            .with_header("x-upstream-service", "convex")
    })?;

    let status = response.status();
    let body: Value = response.json().await.map_err(|error| {
        WebError::bad_gateway_code(
            "convex_bad_response",
            format!("Convex 响应解析失败: {error}"),
        )
        .with_context(context)
        .with_header("x-error-phase", "query_decode")
        .with_header("x-upstream-service", "convex")
        .with_header("x-upstream-status", status.as_u16().to_string())
    })?;
    if !status.is_success() {
        let message = body
            .get("errorMessage")
            .and_then(Value::as_str)
            .unwrap_or("Convex query 失败");
        return Err(
            WebError::bad_gateway_code("convex_upstream_error", message.to_string())
                .with_context(context)
                .with_header("x-error-phase", "query_status")
                .with_header("x-upstream-service", "convex")
                .with_header("x-upstream-status", status.as_u16().to_string()),
        );
    }

    match body.get("status").and_then(Value::as_str) {
        Some("success") => Ok(body.get("value").cloned().unwrap_or(Value::Null)),
        Some("error") => Err(WebError::bad_gateway_code(
            "convex_upstream_error",
            body.get("errorMessage")
                .and_then(Value::as_str)
                .unwrap_or("Convex 返回 error")
                .to_string(),
        )
        .with_context(context)
        .with_header("x-error-phase", "query_payload")
        .with_header("x-upstream-service", "convex")
        .with_header("x-upstream-status", status.as_u16().to_string())),
        _ => Err(WebError::bad_gateway_code(
            "convex_bad_response",
            format!("未知 Convex 响应: {body}"),
        )
        .with_context(context)
        .with_header("x-error-phase", "query_payload")
        .with_header("x-upstream-service", "convex")
        .with_header("x-upstream-status", status.as_u16().to_string())),
    }
}

pub async fn execute_sidebar_dataset_query(
    config: &AppConfig,
    context: &RequestContext,
    plan: &RuntimeQueryExecutionPlan,
) -> Result<Value, WebError> {
    execute_convex_query_plan(config, context, plan).await
}

pub async fn execute_convex_command_plan(
    config: &AppConfig,
    context: &RequestContext,
    plan: &RuntimeCommandExecutionPlan,
) -> Result<Value, WebError> {
    if plan.function_name.trim().is_empty() || plan.function_name.ends_with(":unknown") {
        return Err(WebError::bad_request_code(
            "transport_command_unsupported",
            format!(
                "mnote-web transport 暂不支持 command: {}",
                plan.function_name
            ),
        )
        .with_context(context)
        .with_header("x-error-phase", "plan_validation"));
    }

    if let Some(fixture) = load_mutation_fixture(config, context, plan)? {
        return Ok(fixture);
    }

    let payload = json!({
        "path": plan.function_name,
        "format": "convex_encoded_json",
        "args": [plan.args_json.clone()],
    });

    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(20))
        .build()
        .map_err(|error| {
            WebError::internal(format!("Convex HTTP 客户端创建失败: {error}"))
                .with_context(context)
                .with_header("x-error-phase", "client_build")
                .with_header("x-upstream-service", "convex")
        })?;

    let mut request = client
        .post(format!("{}/api/mutation", convex_url(config, context)?))
        .header("Authorization", build_authorization(config, context)?)
        .header("Content-Type", "application/json")
        .header("Convex-Client", "mnote-web")
        .header(HEADER_REQUEST_ID, &context.trace.request_id)
        .header(HEADER_TRACE_ID, &context.trace.trace_id)
        .header(HEADER_SOURCE_CHANNEL, context.source.channel.as_str())
        .header(HEADER_SOURCE_CLIENT, context.source.client.as_str())
        .json(&payload);

    if let Some(workspace_id) = plan
        .workspace_id
        .as_deref()
        .or(context.workspace.workspace_id.as_deref())
    {
        request = request.header(HEADER_WORKSPACE_ID, workspace_id);
    }
    if let Some(idempotency_key) = plan
        .idempotency_key
        .as_deref()
        .or(context.source.idempotency_key.as_deref())
        .map(str::trim)
        .filter(|value| !value.is_empty())
    {
        request = request.header(HEADER_IDEMPOTENCY_KEY, idempotency_key);
    }

    let response = request.send().await.map_err(|error| {
        let base = if error.is_timeout() {
            WebError::gateway_timeout_code(
                "convex_timeout",
                format!("Convex mutation 超时: {error}"),
            )
        } else {
            WebError::service_unavailable_code(
                "convex_unavailable",
                format!("Convex mutation 请求失败: {error}"),
            )
        };
        base.with_context(context)
            .with_header("x-error-phase", "mutation_send")
            .with_header("x-upstream-service", "convex")
    })?;

    let status = response.status();
    let body: Value = response.json().await.map_err(|error| {
        WebError::bad_gateway_code(
            "convex_bad_response",
            format!("Convex mutation 响应解析失败: {error}"),
        )
        .with_context(context)
        .with_header("x-error-phase", "mutation_decode")
        .with_header("x-upstream-service", "convex")
        .with_header("x-upstream-status", status.as_u16().to_string())
    })?;
    if !status.is_success() {
        let message = body
            .get("errorMessage")
            .and_then(Value::as_str)
            .unwrap_or("Convex mutation 失败");
        return Err(
            WebError::bad_gateway_code("convex_upstream_error", message.to_string())
                .with_context(context)
                .with_header("x-error-phase", "mutation_status")
                .with_header("x-upstream-service", "convex")
                .with_header("x-upstream-status", status.as_u16().to_string()),
        );
    }

    match body.get("status").and_then(Value::as_str) {
        Some("success") => Ok(body.get("value").cloned().unwrap_or(Value::Null)),
        Some("error") => Err(WebError::bad_gateway_code(
            "convex_upstream_error",
            body.get("errorMessage")
                .and_then(Value::as_str)
                .unwrap_or("Convex 返回 error")
                .to_string(),
        )
        .with_context(context)
        .with_header("x-error-phase", "mutation_payload")
        .with_header("x-upstream-service", "convex")
        .with_header("x-upstream-status", status.as_u16().to_string())),
        _ => Err(WebError::bad_gateway_code(
            "convex_bad_response",
            format!("未知 Convex 响应: {body}"),
        )
        .with_context(context)
        .with_header("x-error-phase", "mutation_payload")
        .with_header("x-upstream-service", "convex")
        .with_header("x-upstream-status", status.as_u16().to_string())),
    }
}

#[cfg(test)]
mod tests {
    use super::build_authorization;
    use crate::app::AppConfig;
    use crate::context::RequestContext;
    use axum::http::{HeaderMap, HeaderValue, Method, Uri};

    fn config() -> AppConfig {
        AppConfig {
            service_name: "mnote-web".into(),
            service_version: "0.1.0".into(),
            bind_addr: "127.0.0.1:0".into(),
            enable_debug_shell_routes: false,
            hermes_base_path: "/api/hermes".into(),
            compat_next_base_path: "/api/compat/next".into(),
            convex_url: Some("http://127.0.0.1:3210".into()),
            convex_admin_key: Some("admin-demo".into()),
            allow_dev_fixtures: false,
            query_fixtures_json: None,
            mutation_fixtures_json: None,
            dev_user_id: "dev-user".into(),
            dev_user_name: "开发用户".into(),
            dev_user_email: "dev@mnote.local".into(),
        }
    }

    fn request_context(headers: HeaderMap) -> RequestContext {
        RequestContext::from_http_parts(
            &Method::GET,
            &"/api/compat/next/sidebar?workspaceId=ws_demo"
                .parse::<Uri>()
                .expect("uri"),
            &headers,
        )
    }

    #[test]
    fn build_authorization_prefers_forwarded_authorization() {
        let mut headers = HeaderMap::new();
        headers.insert(
            "authorization",
            HeaderValue::from_static("Bearer real-token"),
        );

        let authorization =
            build_authorization(&config(), &request_context(headers)).expect("authorization");

        assert_eq!(authorization, "Bearer real-token");
    }

    #[test]
    fn build_authorization_falls_back_to_plain_admin_auth() {
        let authorization = build_authorization(&config(), &request_context(HeaderMap::new()))
            .expect("authorization");

        assert_eq!(authorization, "Convex admin-demo");
    }

    #[test]
    fn build_authorization_reads_convex_token_from_cookie() {
        let mut headers = HeaderMap::new();
        headers.insert(
            "cookie",
            HeaderValue::from_static(
                "foo=bar; mnote_web_convex_token=token-from-cookie; theme=light",
            ),
        );

        let authorization =
            build_authorization(&config(), &request_context(headers)).expect("authorization");

        assert_eq!(authorization, "Bearer token-from-cookie");
    }
}
