use mumu::parser::interpreter::{Interpreter, DynamicFnInfo};
use mumu::parser::types::{FunctionValue, Value};
use crate::register::credentials::SESSIONS;
use std::sync::{Arc, Mutex};
use reqwest::blocking::Client;
use reqwest::header::{HeaderValue, CONTENT_TYPE, USER_AGENT};
use chrono::Utc;
use xmltree::Element;
use hex;
use indexmap::IndexMap;
use std::borrow::Cow;
use crate::functions::sigv4::{build_aws_sigv4_headers, hex_sha256_str};

/// --------------------------------------------------------------------------
/// register fn
/// --------------------------------------------------------------------------
pub fn register_s3_list(interp: &mut Interpreter) {
    let f = Arc::new(Mutex::new(|interp: &mut Interpreter, args: Vec<Value>| {
        if interp.is_verbose() {
            eprintln!("[aws:s3:list] called with args: {:?}", args);
        }
        let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            aws_s3_list_function(interp, args)
        }));
        match res {
            Ok(r) => r,
            Err(e) => {
                eprintln!("[aws:s3:list] PANIC: {e:?}");
                Err(format!("aws:s3:list: fatal plugin panic: {e:?}"))
            }
        }
    }));
    let info = DynamicFnInfo::new(f, false);
    interp.register_dynamic_function_ex("aws:s3:list", info);

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

/// --------------------------------------------------------------------------
/// main implementation
/// --------------------------------------------------------------------------
pub fn aws_s3_list_function(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    // ——— argument handling ————————————————————————————————
    if args.len() != 1 {
        return Err("aws:s3:list expects a single options keyed array argument.".to_string());
    }
    let options = match &args[0] {
        Value::KeyedArray(map) => map.clone(),
        _ => {
            return Err(
                "aws:s3:list: argument must be a keyed array (options incl. required ‘credentials’)".to_string(),
            )
        }
    };

    // required session handler
    let handler = match options.get("credentials") {
        Some(Value::SingleString(h)) => h,
        Some(Value::StrArray(arr)) if arr.len() == 1 => &arr[0],
        _ => {
            return Err("aws:s3:list: options must include a string property 'credentials'".to_string())
        }
    };

    // optional bucket to list
    let maybe_bucket = options.get("bucket").and_then(|v| match v {
        Value::SingleString(s) => Some(s.clone()),
        Value::StrArray(arr) if arr.len() == 1 => Some(arr[0].clone()),
        _ => None,
    });

    // ——— session data ————————————————————————————————————
    let sessions = SESSIONS.lock().unwrap();
    let session = sessions
        .get(handler)
        .ok_or_else(|| format!("No session found with handler ID: {}", handler))?;
    let access_key = &session.access_key;
    let secret_key = &session.secret_key;
    let region = &session.region;
    let endpoint_opt = session.endpoint.clone();

    // ——— build request pieces ————————————————————————————
    let service = "s3";

    // variables we will fill:
    let (host, endpoint_url, canonical_uri, canonical_querystring, is_list_bucket) =
        if let Some(bucket) = maybe_bucket.clone() {
            // ── LIST *OBJECTS* IN A BUCKET ──────────────────────
            let query_qs = "list-type=2";
            if let Some(ep) = endpoint_opt {
                // path‑style:  http://localhost:4566/{bucket}?list-type=2
                let ep_trim = ep.trim_end_matches('/');
                let host = ep_trim
                    .trim_start_matches("http://")
                    .trim_start_matches("https://")
                    .to_string();
                let url = format!("{ep_trim}/{bucket}?{query_qs}");
                (
                    host,
                    url,
                    format!("/{bucket}"),
                    query_qs.to_string(),
                    false,
                )
            } else {
                // virtual‑host style: https://{bucket}.s3.amazonaws.com/?list-type=2
                let host = format!("{bucket}.s3.amazonaws.com");
                let url = format!("https://{host}/?{query_qs}");
                (host, url, "/".to_string(), query_qs.to_string(), false)
            }
        } else {
            // ── LIST *ALL* BUCKETS ───────────────────────────────
            if let Some(ep) = endpoint_opt {
                // path‑style list‑buckets:  http://localhost:4566/
                let ep_trim = ep.trim_end_matches('/');
                let host = ep_trim
                    .trim_start_matches("http://")
                    .trim_start_matches("https://")
                    .to_string();
                (host, format!("{ep_trim}/"), "/".to_string(), "".to_string(), true)
            } else {
                // AWS default list‑buckets: https://s3.amazonaws.com/
                (
                    "s3.amazonaws.com".to_string(),
                    "https://s3.amazonaws.com/".to_string(),
                    "/".to_string(),
                    "".to_string(),
                    true,
                )
            }
        };

    // ——— date & signature pieces ————————————————————————
    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 method = "GET";
    let payload_hash = hex_sha256_str("");

    let (_authz, mut headers) = build_aws_sigv4_headers(
        method,
        &canonical_uri,
        &canonical_querystring,
        &payload_hash,
        &host,
        &amz_date,
        &date_stamp,
        region,
        service,
        access_key,
        secret_key,
    );
    headers.insert(USER_AGENT, HeaderValue::from_static("aws-mumu/0.1"));
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/xml"));

    // ——— execute HTTP call ———————————————————————————————
    let client = Client::builder()
        .build()
        .map_err(|e| format!("Failed to build reqwest client: {e}"))?;
    let resp = client
        .get(&endpoint_url)
        .headers(headers)
        .send()
        .map_err(|e| format!("aws:s3:list: Request error: {e}"))?;

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

    // ----- non‑2xx → parse AWS error XML ---------------------
    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(Cow::Borrowed(""))
                .to_string();
            let msg = root
                .get_child("Message")
                .and_then(|c| c.get_text())
                .unwrap_or(Cow::Borrowed(""))
                .to_string();
            if !code.is_empty() || !msg.is_empty() {
                return Ok(Value::KeyedArray(
                    IndexMap::from_iter([
                        ("ok".into(), Value::Bool(false)),
                        ("error".into(), Value::SingleString(format!("aws:s3:list: {code}: {msg}"))),
                    ]),
                ));
            }
        }
        return Ok(Value::KeyedArray(IndexMap::from_iter([
            ("ok".into(), Value::Bool(false)),
            (
                "error".into(),
                Value::SingleString(format!(
                    "aws:s3:list: HTTP error status {}",
                    status.as_u16()
                )),
            ),
        ])));
    }

    // ----- 2xx → parse listing XML ---------------------------
    let root = Element::parse(body.as_bytes())
        .map_err(|e| format!("aws:s3:list: XML parse error: {e}"))?;

    if is_list_bucket {
        // respond with bucket names
        let mut buckets = Vec::new();
        if let Some(bs) = root.get_child("Buckets") {
            for b in bs.children.iter().filter_map(|n| n.as_element()) {
                if b.name == "Bucket" {
                    if let Some(name_el) = b.get_child("Name") {
                        if let Some(bucket_name) = name_el.get_text() {
                            buckets.push(bucket_name.to_string());
                        }
                    }
                }
            }
        }
        Ok(Value::StrArray(buckets))
    } else {
        // respond with object keys
        let mut keys = Vec::new();
        for child in root.children.iter().filter_map(|n| n.as_element()) {
            if child.name == "Contents" {
                if let Some(name_el) = child.get_child("Key") {
                    if let Some(object_key) = name_el.get_text() {
                        keys.push(object_key.to_string());
                    }
                }
            }
        }
        Ok(Value::StrArray(keys))
    }
}
