use serde_json::Value;

use crate::creds::Credentials;
use crate::error::Error;
use crate::http::{self, Request, Response};
use crate::region::Region;

const CONTENT_TYPE: &str = "application/x-amz-json-1.1";

pub fn call(
    creds: &Credentials,
    region: &Region,
    service: &str,
    target: &str,
    body: &Value,
) -> Result<Value, Error> {
    let payload =
        serde_json::to_vec(body).map_err(|e| Error::Invalid(format!("encode json: {e}")))?;
    let req = Request::new("POST", service)
        .header("Content-Type", CONTENT_TYPE)
        .header("X-Amz-Target", target)
        .body(payload);

    let resp = http::send(creds, region, &req)?;
    if resp.is_success() {
        if resp.body.is_empty() {
            return Ok(Value::Null);
        }
        serde_json::from_slice(&resp.body)
            .map_err(|e| Error::parse(format!("decode json response: {e}")))
    } else {
        Err(json_error(resp))
    }
}

fn json_error(resp: Response) -> Error {
    let status = resp.status;
    let Ok(value): Result<Value, _> = serde_json::from_slice(&resp.body) else {
        return Error::Http {
            status,
            body: resp.body_str(),
        };
    };
    let code = value
        .get("__type")
        .and_then(Value::as_str)
        .map(|t| t.rsplit(['#', '/']).next().unwrap_or(t).to_string())
        .unwrap_or_else(|| "Unknown".to_string());
    let message = value
        .get("message")
        .or_else(|| value.get("Message"))
        .and_then(Value::as_str)
        .unwrap_or("")
        .to_string();
    Error::Aws {
        code,
        message,
        status,
    }
}
