use crate::app::AppConfig;
use crate::context::RequestContext;
use crate::error::WebError;
use bridge_runtime::{
    build_runtime_command_artifact_plan, retired_command_transport_function_name,
    retired_query_transport_function_name, RuntimeCommandArtifactPlan, RuntimeCommandExecutionPlan,
    RuntimeQueryExecutionPlan,
};
use serde_json::Value;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};

#[derive(Debug)]
pub struct RetiredCloudCommandExecution {
    pub result: Value,
    pub artifacts: Option<RuntimeCommandArtifactPlan>,
    pub artifact_error: Option<String>,
}

fn retired_error(context: &RequestContext, phase: &'static str) -> WebError {
    WebError::service_unavailable_code(
        "convex_retired",
        "旧 cloud/Convex 兼容运行时已退役；请使用 local-first Rust/SQLite control-plane 路径",
    )
    .with_context(context)
    .with_header("x-error-phase", phase)
    .with_header("x-upstream-service", "legacy-cloud-retired")
}

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")
    })?;

    let Some(map) = fixtures.as_object() else {
        return Ok(None);
    };
    if let Some(fixture) = map.get(plan.function_name.as_str()) {
        return Ok(Some(fixture.clone()));
    }
    Ok(retired_query_transport_function_name(&plan.query_name)
        .ok()
        .and_then(|legacy_name| map.get(legacy_name))
        .cloned())
}

fn load_mutation_fixture_by_name(
    config: &AppConfig,
    context: &RequestContext,
    function_name: &str,
) -> 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(function_name))
        .cloned())
}

fn load_mutation_fixture(
    config: &AppConfig,
    context: &RequestContext,
    plan: &RuntimeCommandExecutionPlan,
) -> Result<Option<Value>, WebError> {
    if let Some(fixture) =
        load_mutation_fixture_by_name(config, context, plan.function_name.as_str())?
    {
        return Ok(Some(fixture));
    }
    if let Some(legacy_name) = resource_lifecycle_transport_function_name(plan) {
        if let Some(fixture) = load_mutation_fixture_by_name(config, context, legacy_name)? {
            return Ok(Some(fixture));
        }
    }
    retired_command_transport_function_name(&plan.command_name)
        .ok()
        .map(|legacy_name| load_mutation_fixture_by_name(config, context, legacy_name))
        .transpose()
        .map(|fixture| fixture.flatten())
}

fn resource_lifecycle_transport_function_name(
    plan: &RuntimeCommandExecutionPlan,
) -> Option<&'static str> {
    let action = match plan.command_name.as_str() {
        "tree.resource.archive" => "archive",
        "tree.resource.restore" => "restore",
        "tree.resource.purge" => "purge",
        "tree.resource.rename" => "rename",
        _ => return None,
    };
    let resource_kind = plan
        .args_json
        .get("resourceLifecyclePlan")
        .and_then(|value| value.get("resourceKind"))
        .or_else(|| plan.args_json.get("resourceKind"))
        .and_then(Value::as_str)?;
    match (action, resource_kind) {
        ("archive" | "restore" | "rename", "file" | "media") => Some("mediaAssets:patchById"),
        ("purge", "file" | "media") => Some("mediaAssets:purgeById"),
        ("archive", "mindmap") => Some("mindmaps:softDelete"),
        ("restore", "mindmap") => Some("mindmaps:restore"),
        ("purge", "mindmap") => Some("mindmaps:purge"),
        ("archive", "table") => Some("tables:remove"),
        ("restore", "table") => Some("tables:restore"),
        ("purge", "table") => Some("tables:purge"),
        ("rename", "table") => Some("tables:update"),
        _ => None,
    }
}

pub async fn execute_retired_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);
    }

    Err(retired_error(context, "convex_query_retired"))
}

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

pub async fn execute_retired_query_by_name(
    config: &AppConfig,
    context: &RequestContext,
    function_name: &str,
    args: Value,
    workspace_id: Option<&str>,
    error_phase: &'static str,
) -> Result<Value, WebError> {
    let plan = RuntimeQueryExecutionPlan {
        query_name: function_name.to_string(),
        function_name: function_name.to_string(),
        workspace_id: workspace_id.map(ToOwned::to_owned),
        request_id: context.trace.request_id.clone(),
        trace_id: context.trace.trace_id.clone(),
        actor_id: context.auth.actor_id.clone(),
        payload_json: args.to_string(),
        args_json: args,
    };
    execute_retired_query_plan(config, context, &plan)
        .await
        .map_err(|error| error.with_header("x-error-phase", error_phase))
}

pub async fn execute_retired_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);
    }

    Err(retired_error(context, "convex_mutation_retired"))
}

pub async fn execute_retired_mutation_by_name(
    config: &AppConfig,
    context: &RequestContext,
    function_name: &str,
    args: Value,
    _workspace_id: Option<&str>,
    _idempotency_key: Option<&str>,
    error_phase: &'static str,
) -> Result<Value, WebError> {
    if let Some(fixture) = load_mutation_fixture_by_name(config, context, function_name)? {
        return Ok(fixture);
    }

    let _ = args;
    Err(retired_error(context, error_phase))
}

pub async fn persist_runtime_command_artifacts(
    _config: &AppConfig,
    _context: &RequestContext,
    _artifacts: &RuntimeCommandArtifactPlan,
) -> Result<(), WebError> {
    // Convex 已退役。Rust 侧仍会把 artifact plan 返回给调用方和 realtime
    // consumer；这里保持 no-op，避免兼容路径因为历史持久化层退役而失败。
    Ok(())
}

pub async fn execute_retired_command_plan_with_artifacts(
    config: &AppConfig,
    context: &RequestContext,
    runtime_context: &bridge_runtime::RuntimeBridgeContextWire,
    command: &bridge_runtime::RuntimeCommandEnvelopeWire,
    plan: &RuntimeCommandExecutionPlan,
) -> Result<RetiredCloudCommandExecution, WebError> {
    let result = execute_retired_command_plan(config, context, plan).await?;
    let now = OffsetDateTime::now_utc()
        .format(&Rfc3339)
        .unwrap_or_else(|_| "1970-01-01T00:00:00Z".into());
    let artifacts =
        build_runtime_command_artifact_plan(runtime_context, command, plan, &result, &now);
    let artifact_error = if let Some(artifacts) = artifacts.as_ref() {
        persist_runtime_command_artifacts(config, context, artifacts)
            .await
            .err()
            .map(|error| error.message().to_string())
    } else {
        None
    };
    Ok(RetiredCloudCommandExecution {
        result,
        artifacts,
        artifact_error,
    })
}
