use crate::engine::{HTTP_MODULE, create_engine, load_raw_files};
use crate::request::build_request_value;
use crate::response::runtime_value_to_response;
use crate::state::AppState;
use axum::{
    body::Bytes,
    extract::{ConnectInfo, Query, State},
    http::{HeaderMap, Method, StatusCode, Uri, Version, request::Parts},
    response::{IntoResponse, Response},
};
use mq_lang::RuntimeValue;
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::Arc;

/// Extracts the remote address from the connection.
/// Falls back to "127.0.0.1" when ConnectInfo is not available (e.g. Unix socket mode).
pub struct RemoteAddr(pub String);

impl<S> axum::extract::FromRequestParts<S> for RemoteAddr
where
    S: Send + Sync,
{
    type Rejection = Infallible;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        if let Some(ConnectInfo(addr)) = parts.extensions.get::<ConnectInfo<SocketAddr>>() {
            Ok(RemoteAddr(addr.to_string()))
        } else {
            Ok(RemoteAddr("127.0.0.1".to_string()))
        }
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn handler(
    remote: RemoteAddr,
    State(state): State<Arc<AppState>>,
    method: Method,
    uri: Uri,
    version: Version,
    headers: HeaderMap,
    Query(params): Query<BTreeMap<String, String>>,
    body_bytes: Bytes,
) -> Response {
    let script = match state.script_content.read().unwrap().clone() {
        Some(s) => s,
        None => return (StatusCode::INTERNAL_SERVER_ERROR, "Script not loaded").into_response(),
    };

    let req_value = build_request_value(
        &remote.0,
        &method,
        &uri,
        version,
        &headers,
        params,
        &body_bytes,
    );

    run_script(&state, &script, req_value)
}

fn run_script(state: &AppState, user_script: &str, req_value: RuntimeValue) -> Response {
    // Create a fresh engine per request - fully isolated, no shared mutable state.
    let mut engine = create_engine(&state.args);
    if let Err(e) = load_raw_files(&engine, &state.args) {
        tracing::error!("Failed to load raw files: {:?}", e);
    }

    // Prepend the built-in http module so users can call h::ok(), h::json_ok(), etc.
    // `let req = .` makes the request available as a named variable in scripts.
    let full_script = format!(
        "module h:\n{}\nend\n| let req = . | {}",
        HTTP_MODULE, user_script
    );

    match engine.eval(&full_script, std::iter::once(req_value.clone())) {
        Ok(values) => {
            let value = values
                .values()
                .last()
                .cloned()
                .unwrap_or(RuntimeValue::NONE);

            if value.is_function() {
                // Script is a function handler — re-evaluate calling it with req.
                call_function_handler(state, user_script, req_value)
            } else {
                runtime_value_to_response(value, &state.args.format)
            }
        }
        Err(e) => {
            tracing::error!("Script error: {:?}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Script error: {:?}", e),
            )
                .into_response()
        }
    }
}

/// When a script evaluates to a function, call it with `req` as the argument.
fn call_function_handler(state: &AppState, user_script: &str, req_value: RuntimeValue) -> Response {
    let mut engine = create_engine(&state.args);
    if let Err(e) = load_raw_files(&engine, &state.args) {
        tracing::error!("Failed to load raw files: {:?}", e);
    }

    // Wrap script in parens and invoke it with req.
    let call_code = format!(
        "module h:\n{}\nend\n| let req = . | let _h = ({}) | _h(req)",
        HTTP_MODULE, user_script
    );

    match engine.eval(&call_code, std::iter::once(req_value)) {
        Ok(values) => {
            let value = values
                .values()
                .last()
                .cloned()
                .unwrap_or(RuntimeValue::NONE);
            runtime_value_to_response(value, &state.args.format)
        }
        Err(e) => {
            tracing::error!("Function call error: {:?}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Function call error: {:?}", e),
            )
                .into_response()
        }
    }
}
