use mumu::parser::interpreter::{Interpreter, DynamicFnInfo};
use mumu::parser::types::{FunctionValue, Value};
use crate::register::credentials::SESSIONS;

use crate::functions::sigv4::{build_aws_sigv4_headers, hex_sha256};

use chrono::Utc;
use indexmap::IndexMap;
use reqwest::blocking::Client;
use reqwest::header::{HeaderValue, CONTENT_TYPE, USER_AGENT, CONTENT_LENGTH};
use std::fs;
use std::path::Path;
use std::sync::{Arc, Mutex};
use xmltree::Element;

/* ────────────────────────────────────────────────────────────────────────── */
fn make_error(msg: impl Into<String>) -> Value {
    let mut m = IndexMap::new();
    m.insert("ok".into(), Value::Bool(false));
    m.insert("error".into(), Value::SingleString(msg.into()));
    Value::KeyedArray(m)
}
/* ────────────────────────────────────────────────────────────────────────── */

pub fn register_s3_upload(interp: &mut Interpreter) {
    let f = Arc::new(Mutex::new(
        |interp: &mut Interpreter, args: Vec<Value>| -> Result<Value, String> {
            if interp.is_verbose() {
                eprintln!("[aws:s3:upload] called with args: {:?}", args);
            }
            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                aws_s3_upload_function(interp, args)
            }));
            match res {
                Ok(Ok(v)) => Ok(v),
                Ok(Err(msg)) => Ok(make_error(format!("aws:s3:upload: {}", msg))),
                Err(e) => Ok(make_error(format!(
                    "aws:s3:upload: fatal plugin panic: {:?}",
                    e
                ))),
            }
        },
    ));
    let info = DynamicFnInfo::new(f, false);
    interp.register_dynamic_function_ex("aws:s3:upload", info);

    interp.set_variable(
        "aws:s3:upload",
        Value::Function(Box::new(FunctionValue::Named("aws:s3:upload".into()))),
    );
}

pub fn aws_s3_upload_function(
    interp: &mut Interpreter,
    args: Vec<Value>,
) -> Result<Value, String> {
    if interp.is_verbose() {
        eprintln!("[aws:s3:upload] entering with args: {:?}", args);
    }
    /* ---------- Args ---------------------------------------------------- */
    if args.len() != 1 {
        return Err("expects a single keyed‑array argument".into());
    }
    let opts = match &args[0] {
        Value::KeyedArray(m) => m,
        _ => return Err("argument must be a keyed array".into()),
    };

    let cred_id = match opts.get("credentials") {
        Some(Value::SingleString(s)) => s,
        _ => return Err("missing 'credentials'".into()),
    };
    let bucket = match opts.get("bucket") {
        Some(Value::SingleString(s)) => s,
        _ => return Err("missing 'bucket'".into()),
    };
    let path_str = match opts.get("path") {
        Some(Value::SingleString(s)) => s,
        _ => return Err("missing 'path'".into()),
    };

    /* ---------- Session ------------------------------------------------- */
    let sessions = SESSIONS.lock().unwrap();
    let sess = sessions
        .get(cred_id)
        .ok_or_else(|| format!("no session with id {}", cred_id))?;

    /* ---------- File ---------------------------------------------------- */
    let path = Path::new(path_str);
    let file_content =
        fs::read(path).map_err(|e| format!("read file '{}': {}", path_str, e))?;
    let file_len = file_content.len();
    let object_key = path
        .file_name()
        .and_then(|s| s.to_str())
        .ok_or_else(|| format!("cannot determine object key from '{}'", path_str))?;

    /* ---------- Endpoint & SigV4 ---------------------------------------- */
    let host = format!("{}.s3.amazonaws.com", bucket);
    let endpoint_url = if let Some(ep) = &sess.endpoint {
        format!("{}/{}/{}", ep.trim_end_matches('/'), bucket, object_key)
    } else {
        format!("https://{}/{}", host, object_key)
    };

    let now = Utc::now();
    let amz_date = now.format("%Y%m%dT%H%M%SZ").to_string();
    let date_stamp = now.format("%Y%m%d").to_string();

    let (authz, mut headers) = build_aws_sigv4_headers(
        "PUT",
        &format!("/{}", object_key),
        "",
        &hex_sha256(&file_content),
        &host,
        &amz_date,
        &date_stamp,
        &sess.region,
        "s3",
        &sess.access_key,
        &sess.secret_key,
    );
    headers.insert(USER_AGENT, HeaderValue::from_static("aws-mumu/0.1"));
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"));
    headers.insert(
        CONTENT_LENGTH,
        HeaderValue::from_str(&file_len.to_string()).unwrap(),
    );

    if interp.is_verbose() {
        eprintln!(
            "[aws:s3:upload] Built SigV4 headers: {:?}, authz={}",
            headers, authz
        );
        eprintln!(
            "[aws:s3:upload] PUT {} ({} bytes)",
            endpoint_url, file_len
        );
    }

    let client = Client::builder()
        .build()
        .map_err(|e| format!("build client: {}", e))?;

    let res = client
        .put(&endpoint_url)
        .headers(headers)
        .body(file_content)
        .send()
        .map_err(|e| format!("HTTP error: {}", e))?;

    let status = res.status();
    let body = res.text().unwrap_or_default();

    if !status.is_success() {
        if let Ok(root) = Element::parse(body.as_bytes()) {
            let code = root
                .get_child("Code")
                .and_then(|c| c.get_text())
                .unwrap_or_default();
            let msg = root
                .get_child("Message")
                .and_then(|c| c.get_text())
                .unwrap_or_default();
            if !code.is_empty() || !msg.is_empty() {
                return Err(format!("{}: {}", code, msg));
            }
        }
        return Err(format!("AWS error status {}", status));
    }

    if interp.is_verbose() {
        eprintln!("[aws:s3:upload] uploaded file: {}", object_key);
    }
    let mut ok = IndexMap::new();
    ok.insert("ok".into(), Value::Bool(true));
    ok.insert("key".into(), Value::SingleString(object_key.into()));
    Ok(Value::KeyedArray(ok))
}
