#![cfg(feature = "ssr")]

use crate::scgi::{send_request, ScgiError, ScgiRequest};
use quick_xml::de::from_str;
use quick_xml::se::to_string;
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum XmlRpcError {
    #[error("SCGI Error: {0}")]
    Scgi(#[from] ScgiError),
    #[error("Serialization Error: {0}")]
    Serialization(String),
    #[error("Deserialization Error: {0}")]
    Deserialization(#[from] quick_xml::de::DeError),
    #[error("XML Parse Error: {0}")]
    Parse(String),
}

// --- Request Parameters Enum ---
#[derive(Debug, Clone)]
pub enum RpcParam {
    String(String),
    Int(i64),
}

impl From<&str> for RpcParam {
    fn from(s: &str) -> Self {
        RpcParam::String(s.to_string())
    }
}

impl From<String> for RpcParam {
    fn from(s: String) -> Self {
        RpcParam::String(s)
    }
}

impl From<i64> for RpcParam {
    fn from(i: i64) -> Self {
        RpcParam::Int(i)
    }
}

impl From<i32> for RpcParam {
    fn from(i: i32) -> Self {
        RpcParam::Int(i as i64)
    }
}

// --- Request Models ---

#[derive(Debug, Serialize)]
#[serde(rename = "methodCall")]
struct MethodCall<'a> {
    #[serde(rename = "methodName")]
    method_name: &'a str,
    params: RequestParams<'a>,
}

#[derive(Debug, Serialize)]
struct RequestParams<'a> {
    param: Vec<RequestParam<'a>>,
}

#[derive(Debug, Serialize)]
struct RequestParam<'a> {
    value: RequestValueInner<'a>,
}

#[derive(Debug, Serialize)]
struct RequestValueInner<'a> {
    #[serde(skip_serializing_if = "Option::is_none")]
    string: Option<&'a str>,
    // rTorrent standard is often i4 (32-bit signed int). i8 might not be supported by all XML-RPC libs.
    // Casting i64 to i32 is safe for typical speed limits.
    #[serde(skip_serializing_if = "Option::is_none")]
    i4: Option<i32>,
}

// --- Response Models for d.multicall2 ---

#[derive(Debug, Deserialize)]
#[serde(rename = "methodResponse")]
struct MulticallResponse {
    params: MulticallResponseParams,
}

#[derive(Debug, Deserialize)]
struct MulticallResponseParams {
    param: MulticallResponseParam,
}

#[derive(Debug, Deserialize)]
struct MulticallResponseParam {
    value: MulticallResponseValueArray,
}

#[derive(Debug, Deserialize)]
struct MulticallResponseValueArray {
    array: MulticallResponseDataOuter,
}

#[derive(Debug, Deserialize)]
struct MulticallResponseDataOuter {
    data: MulticallResponseDataOuterValue,
}

#[derive(Debug, Deserialize)]
struct MulticallResponseDataOuterValue {
    #[serde(rename = "value", default)]
    values: Vec<MulticallRowValue>,
}

#[derive(Debug, Deserialize)]
struct MulticallRowValue {
    array: MulticallResponseDataInner,
}

#[derive(Debug, Deserialize)]
struct MulticallResponseDataInner {
    data: MulticallResponseDataInnerValue,
}

#[derive(Debug, Deserialize)]
struct MulticallResponseDataInnerValue {
    #[serde(rename = "value", default)]
    values: Vec<MulticallItemValue>,
}

#[derive(Debug, Deserialize)]
struct MulticallItemValue {
    #[serde(rename = "string", default)]
    string: Option<String>,
    #[serde(rename = "i4", default)]
    i4: Option<i64>,
    #[serde(rename = "i8", default)]
    i8: Option<i64>,
}

impl MulticallItemValue {
    fn to_string_lossy(&self) -> String {
        if let Some(s) = &self.string {
            s.clone()
        } else if let Some(i) = self.i4 {
            i.to_string()
        } else if let Some(i) = self.i8 {
            i.to_string()
        } else {
            String::new()
        }
    }
}

// --- Response Model for simple string ---

#[derive(Debug, Deserialize)]
#[serde(rename = "methodResponse")]
struct StringResponse {
    params: StringResponseParams,
}

#[derive(Debug, Deserialize)]
struct StringResponseParams {
    param: StringResponseParam,
}

#[derive(Debug, Deserialize)]
struct StringResponseParam {
    value: StringResponseValue,
}

#[derive(Debug, Deserialize)]
struct StringResponseValue {
    string: String,
}

// --- Response Model for simple integer (i8/i4) ---

#[derive(Debug, Deserialize)]
#[serde(rename = "methodResponse")]
struct IntegerResponse {
    params: IntegerResponseParams,
}

#[derive(Debug, Deserialize)]
struct IntegerResponseParams {
    param: IntegerResponseParam,
}

#[derive(Debug, Deserialize)]
struct IntegerResponseParam {
    value: IntegerResponseValue,
}

#[derive(Debug, Deserialize)]
struct IntegerResponseValue {
    #[serde(rename = "i8", default)]
    i8: Option<i64>,
    #[serde(rename = "i4", default)]
    i4: Option<i64>,
    #[serde(rename = "string", default)]
    string: Option<String>,
}

// --- Client Implementation ---

pub struct RtorrentClient {
    socket_path: String,
}

impl RtorrentClient {
    pub fn new(socket_path: &str) -> Self {
        Self {
            socket_path: socket_path.to_string(),
        }
    }

    /// Helper to build and serialize XML-RPC method call
    fn build_method_call(&self, method: &str, params: &[RpcParam]) -> Result<String, XmlRpcError> {
        let req_params = RequestParams {
            param: params
                .iter()
                .map(|p| RequestParam {
                    value: match p {
                        RpcParam::String(s) => RequestValueInner {
                            string: Some(s),
                            i4: None,
                        },
                        RpcParam::Int(i) => RequestValueInner {
                            string: None,
                            i4: Some(*i as i32),
                        },
                    },
                })
                .collect(),
        };

        let call = MethodCall {
            method_name: method,
            params: req_params,
        };

        let xml_body = to_string(&call).map_err(|e| XmlRpcError::Serialization(e.to_string()))?;
        Ok(format!("<?xml version=\"1.0\"?>\n{}", xml_body))
    }

    pub async fn call(&self, method: &str, params: &[RpcParam]) -> Result<String, XmlRpcError> {
        let xml = self.build_method_call(method, params)?;
        let req = ScgiRequest::new().body(xml.into_bytes());

        let bytes = send_request(&self.socket_path, req).await?;
        let s = String::from_utf8_lossy(&bytes).to_string();
        Ok(s)
    }
}

pub fn parse_multicall_response(xml: &str) -> Result<Vec<Vec<String>>, XmlRpcError> {
    let response: MulticallResponse = from_str(xml)?;

    let mut result = Vec::new();

    for row in response.params.param.value.array.data.values {
        let mut row_vec = Vec::new();
        for item in row.array.data.values {
            row_vec.push(item.to_string_lossy());
        }
        result.push(row_vec);
    }

    Ok(result)
}

pub fn parse_string_response(xml: &str) -> Result<String, XmlRpcError> {
    let response: StringResponse = from_str(xml)?;
    Ok(response.params.param.value.string)
}

pub fn parse_i64_response(xml: &str) -> Result<i64, XmlRpcError> {
    let response: IntegerResponse = from_str(xml)?;
    if let Some(val) = response.params.param.value.i8 {
        Ok(val)
    } else if let Some(val) = response.params.param.value.i4 {
        Ok(val)
    } else if let Some(ref s) = response.params.param.value.string {
        s.parse()
            .map_err(|_| XmlRpcError::Parse("Not an integer string".to_string()))
    } else {
        Err(XmlRpcError::Parse("No integer value found".to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_method_call() {
        let client = RtorrentClient::new("dummy");
        let params = vec![
            RpcParam::String("".to_string()),
            RpcParam::String("main".to_string()),
            RpcParam::String("d.name=".to_string()),
        ];
        let xml = client.build_method_call("d.multicall2", &params).unwrap();

        assert!(xml.contains("<methodName>d.multicall2</methodName>"));
        assert!(xml.contains("<value><string>main</string></value>"));
    }

    #[test]
    fn test_build_method_call_int() {
        let client = RtorrentClient::new("dummy");
        let params = vec![RpcParam::Int(1024)];
        let xml = client.build_method_call("test.int", &params).unwrap();
        // Should produce <value><i4>1024</i4></value>
        assert!(xml.contains("<i4>1024</i4>"));
    }

    #[test]
    fn test_parse_multicall_response() {
        let xml = r#"<methodResponse>
<params>
<param>
<value>
<array>
<data>
<value>
<array>
<data>
<value><string>HASH123</string></value>
<value><string>Ubuntu ISO</string></value>
<value><i4>1024</i4></value>
</data>
</array>
</value>
</data>
</array>
</value>
</param>
</params>
</methodResponse>
"#;
        let result = parse_multicall_response(xml).expect("Failed to parse");
        assert_eq!(result.len(), 1);
        assert_eq!(result[0][0], "HASH123");
        assert_eq!(result[0][1], "Ubuntu ISO");
        assert_eq!(result[0][2], "1024");
    }
}
