//! Runtime helpers — error envelope, request handle, invalidation/merge
//! resolution. Ports `compute_invalidation` / `compute_merges` /
//! `_resolve_merge_slot` / `_scoped_params` from
//! `backends/mizan-fastapi/src/mizan_fastapi/executor.py:189-263`.

use crate::registry::context_members;
use crate::traits::FunctionSpec;
use serde_json::Value;
use std::any::Any;

/// Type-erased handle to the framework's request object. The HTTP adapter
/// stuffs its native `Request` here; user code casts back via the adapter's
/// helper types.
#[derive(Clone)]
pub struct RequestHandle<'a> {
    pub inner: &'a (dyn Any + Send + Sync),
}

impl<'a> RequestHandle<'a> {
    /// Wrap a typed reference. The most common path — handlers downcast back
    /// to `T` via `downcast::<T>()`.
    pub fn new<T: Any + Send + Sync>(req: &'a T) -> Self {
        Self { inner: req }
    }

    /// Wrap an already-erased `dyn Any` reference. Used by HTTP adapters
    /// that thread an `Arc<dyn Any + Send + Sync>` app state in.
    pub fn from_dyn(req: &'a (dyn Any + Send + Sync)) -> Self {
        Self { inner: req }
    }

    pub fn downcast<T: Any + Send + Sync>(&self) -> Option<&'a T> {
        self.inner.downcast_ref::<T>()
    }
}

/// Mizan's standard error envelope. Mirrors FastAPI's MizanError enum.
#[derive(Debug, Clone)]
pub enum MizanError {
    NotFound(String),
    BadRequest(String),
    ValidationFailed {
        message: String,
        details: Value,
    },
    Unauthorized(String),
    Forbidden(String),
    NotImplementedYet(String),
    InternalError(String),
}

impl MizanError {
    pub fn code(&self) -> &'static str {
        match self {
            MizanError::NotFound(_) => "NOT_FOUND",
            MizanError::BadRequest(_) => "BAD_REQUEST",
            MizanError::ValidationFailed { .. } => "VALIDATION_FAILED",
            MizanError::Unauthorized(_) => "UNAUTHORIZED",
            MizanError::Forbidden(_) => "FORBIDDEN",
            MizanError::NotImplementedYet(_) => "NOT_IMPLEMENTED",
            MizanError::InternalError(_) => "INTERNAL_ERROR",
        }
    }

    pub fn message(&self) -> &str {
        match self {
            MizanError::NotFound(m)
            | MizanError::BadRequest(m)
            | MizanError::Unauthorized(m)
            | MizanError::Forbidden(m)
            | MizanError::NotImplementedYet(m)
            | MizanError::InternalError(m) => m,
            MizanError::ValidationFailed { message, .. } => message,
        }
    }

    pub fn http_status(&self) -> u16 {
        match self {
            MizanError::NotFound(_) => 404,
            MizanError::BadRequest(_) => 400,
            MizanError::ValidationFailed { .. } => 422,
            MizanError::Unauthorized(_) => 401,
            MizanError::Forbidden(_) => 403,
            MizanError::NotImplementedYet(_) => 501,
            MizanError::InternalError(_) => 500,
        }
    }

    /// JSON envelope shape consumers see on the wire.
    pub fn to_json(&self) -> Value {
        let mut body = serde_json::Map::new();
        body.insert("code".into(), Value::String(self.code().into()));
        body.insert("message".into(), Value::String(self.message().into()));
        if let MizanError::ValidationFailed { details, .. } = self {
            body.insert("details".into(), details.clone());
        }
        Value::Object({
            let mut env = serde_json::Map::new();
            env.insert("error".into(), Value::Object(body));
            env
        })
    }
}

/// One entry in the response's `invalidate` array.
#[derive(Debug, Clone)]
pub enum InvalidationTarget {
    /// A whole context is invalidated.
    Context(String),
    /// A context, scoped to specific param values.
    ScopedContext {
        context: String,
        params: serde_json::Map<String, Value>,
    },
    /// A specific function output is invalidated.
    Function(String),
}

impl InvalidationTarget {
    pub fn to_json(&self) -> Value {
        match self {
            InvalidationTarget::Context(name) => Value::String(name.clone()),
            InvalidationTarget::ScopedContext { context, params } => {
                let mut m = serde_json::Map::new();
                m.insert("context".into(), Value::String(context.clone()));
                m.insert("params".into(), Value::Object(params.clone()));
                Value::Object(m)
            }
            InvalidationTarget::Function(name) => {
                let mut m = serde_json::Map::new();
                m.insert("function".into(), Value::String(name.clone()));
                Value::Object(m)
            }
        }
    }
}

/// One entry in the response's `merge` array. Server-resolved slot — the
/// kernel writes the value into `bundle[slot]` directly.
#[derive(Debug, Clone)]
pub struct MergeEntry {
    pub context: String,
    pub slot: String,
    pub value: Value,
    pub params: Option<serde_json::Map<String, Value>>,
}

impl MergeEntry {
    pub fn to_json(&self) -> Value {
        let mut m = serde_json::Map::new();
        m.insert("context".into(), Value::String(self.context.clone()));
        m.insert("slot".into(), Value::String(self.slot.clone()));
        m.insert("value".into(), self.value.clone());
        if let Some(params) = &self.params {
            m.insert("params".into(), Value::Object(params.clone()));
        }
        Value::Object(m)
    }
}

/// Build the `invalidate` list from a function's `affects` metadata,
/// auto-scoping when arg names match context params.
pub fn compute_invalidation(
    fn_spec: &dyn FunctionSpec,
    args: &serde_json::Map<String, Value>,
) -> Vec<InvalidationTarget> {
    fn_spec
        .affects()
        .iter()
        .map(|target| match target {
            crate::ir::AffectTarget::Context(name) => {
                let scoped = scoped_params(name, args);
                if scoped.is_empty() {
                    InvalidationTarget::Context((*name).into())
                } else {
                    InvalidationTarget::ScopedContext {
                        context: (*name).into(),
                        params: scoped,
                    }
                }
            }
            crate::ir::AffectTarget::Function { name, .. } => {
                InvalidationTarget::Function((*name).into())
            }
        })
        .collect()
}

/// Build the `merge` list from a function's `merge` metadata. Each entry
/// names the slot inside the context bundle the return value lands in.
pub fn compute_merges(
    fn_spec: &dyn FunctionSpec,
    args: &serde_json::Map<String, Value>,
    result: &Value,
) -> Vec<MergeEntry> {
    let targets = fn_spec.merge();
    if targets.is_empty() {
        return Vec::new();
    }
    let mutation_output = fn_spec.output_type();
    let mut out = Vec::new();
    for ctx_name in targets {
        let slot = match resolve_merge_slot(ctx_name, mutation_output) {
            Some(s) => s,
            None => continue,
        };
        let scoped = scoped_params(ctx_name, args);
        out.push(MergeEntry {
            context: (*ctx_name).into(),
            slot,
            value: result.clone(),
            params: if scoped.is_empty() {
                None
            } else {
                Some(scoped)
            },
        });
    }
    out
}

/// Find the unique function-name slot whose Output type matches the
/// mutation's Output type. Matches Python's `types_match_for_merge` —
/// structural shape comparison, not name comparison. Returns None on no
/// match or ambiguous match.
fn resolve_merge_slot(context_name: &str, mutation_output: &str) -> Option<String> {
    let mutation_shape = crate::graph_check::resolve_type_shape(mutation_output)?;
    let mut matches: Vec<&'static str> = Vec::new();
    for fn_spec in context_members(context_name) {
        if let Some(candidate_shape) = crate::graph_check::resolve_type_shape(fn_spec.output_type())
        {
            if crate::graph_check::types_match(&candidate_shape, &mutation_shape) {
                matches.push(fn_spec.name());
            }
        }
    }
    if matches.len() == 1 {
        Some(matches[0].into())
    } else {
        None
    }
}

/// Match input args against the context's declared Input field names.
fn scoped_params(
    context_name: &str,
    args: &serde_json::Map<String, Value>,
) -> serde_json::Map<String, Value> {
    let mut declared: std::collections::HashSet<&'static str> = std::collections::HashSet::new();
    for fn_spec in context_members(context_name) {
        for p in fn_spec.input_params() {
            declared.insert(p.name);
        }
    }
    args.iter()
        .filter(|(k, _)| declared.contains(k.as_str()))
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect()
}

