use anyhow::Result;
use base64::Engine;
use serde_json::Value;

use crate::template::schema::ResultDecode;

#[derive(Debug, Clone)]
pub enum DecodedBody {
    Json(Value),
    Text(String),
    Html(String),
    Xml(String),
    Binary(Vec<u8>),
}

impl DecodedBody {
    pub fn as_json(&self) -> Option<&Value> {
        match self {
            DecodedBody::Json(v) => Some(v),
            _ => None,
        }
    }

    pub fn as_text(&self) -> Option<&str> {
        match self {
            DecodedBody::Text(v) | DecodedBody::Html(v) | DecodedBody::Xml(v) => Some(v),
            _ => None,
        }
    }

    pub fn to_json_value(&self) -> Value {
        match self {
            DecodedBody::Json(v) => v.clone(),
            DecodedBody::Text(v) => Value::String(v.clone()),
            DecodedBody::Html(v) => Value::String(v.clone()),
            DecodedBody::Xml(v) => Value::String(v.clone()),
            DecodedBody::Binary(v) => {
                Value::String(base64::engine::general_purpose::STANDARD.encode(v))
            }
        }
    }
}

pub fn decode_response(
    mode: ResultDecode,
    content_type: Option<&str>,
    bytes: &[u8],
) -> Result<DecodedBody> {
    let inferred_mode = match mode {
        ResultDecode::Auto => infer_mode(content_type, bytes),
        explicit => explicit,
    };

    let decoded = match inferred_mode {
        ResultDecode::Auto | ResultDecode::Text => {
            DecodedBody::Text(String::from_utf8_lossy(bytes).to_string())
        }
        ResultDecode::Json => {
            let value: Value = serde_json::from_slice(bytes)?;
            DecodedBody::Json(value)
        }
        ResultDecode::Html => DecodedBody::Html(String::from_utf8_lossy(bytes).to_string()),
        ResultDecode::Xml => DecodedBody::Xml(String::from_utf8_lossy(bytes).to_string()),
        ResultDecode::Binary => DecodedBody::Binary(bytes.to_vec()),
    };

    Ok(decoded)
}

fn infer_mode(content_type: Option<&str>, body: &[u8]) -> ResultDecode {
    if let Some(ct) = content_type {
        let lower = ct.to_ascii_lowercase();
        if lower.contains("application/json") || lower.ends_with("+json") {
            return ResultDecode::Json;
        }
        if lower.contains("text/html") {
            return ResultDecode::Html;
        }
        if lower.contains("xml") {
            return ResultDecode::Xml;
        }
        if lower.starts_with("text/") {
            return ResultDecode::Text;
        }
    }

    if serde_json::from_slice::<Value>(body).is_ok() {
        ResultDecode::Json
    } else {
        ResultDecode::Text
    }
}
