use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use uuid::Uuid;

pub const IPC_PROTOCOL_VERSION: u32 = 1;
pub const ENDPOINT_FILE_ENV: &str = "ALCOMD3_MCP_ENDPOINT_FILE";
pub const ENDPOINT_FILE_NAME: &str = "endpoint.json";
pub const MCP_DATA_DIR_NAME: &str = "mcp";
const ALCOM_DATA_DIR_NAME: &str = "VRChatCreatorCompanion";

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum IpcTransport {
    Tcp,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EndpointMetadata {
    pub protocol_version: u32,
    pub transport: IpcTransport,
    pub host: String,
    pub port: u16,
    pub token: String,
    pub pid: u32,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientIdentity {
    pub session_id: Uuid,
    pub name: String,
    pub version: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IpcRequest {
    pub protocol_version: u32,
    pub token: String,
    pub request_id: Uuid,
    pub client: ClientIdentity,
    pub method: String,
    #[serde(default)]
    pub params: Value,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IpcResponse {
    pub request_id: Uuid,
    pub ok: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<IpcError>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IpcError {
    pub code: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<Value>,
}

impl IpcResponse {
    pub fn success(request_id: Uuid, result: Value) -> Self {
        Self {
            request_id,
            ok: true,
            result: Some(result),
            error: None,
        }
    }

    pub fn error(
        request_id: Uuid,
        code: impl Into<String>,
        message: impl Into<String>,
        data: Option<Value>,
    ) -> Self {
        Self {
            request_id,
            ok: false,
            result: None,
            error: Some(IpcError {
                code: code.into(),
                message: message.into(),
                data,
            }),
        }
    }
}

pub fn endpoint_file_path() -> PathBuf {
    endpoint_file_path_from_env(std::env::var_os(ENDPOINT_FILE_ENV))
}

pub fn endpoint_file_path_from_env(override_path: Option<OsString>) -> PathBuf {
    override_path
        .map(PathBuf::from)
        .unwrap_or_else(|| default_endpoint_file_path(&local_data_root()))
}

pub fn default_endpoint_file_path(local_data_root: &Path) -> PathBuf {
    local_data_root
        .join(ALCOM_DATA_DIR_NAME)
        .join(MCP_DATA_DIR_NAME)
        .join(ENDPOINT_FILE_NAME)
}

pub fn local_data_root() -> PathBuf {
    local_data_root_from_env(
        std::env::var_os("LOCALAPPDATA"),
        std::env::var_os("XDG_DATA_HOME"),
        std::env::var_os("HOME"),
    )
}

pub fn local_data_root_from_env(
    local_app_data: Option<OsString>,
    xdg_data_home: Option<OsString>,
    home: Option<OsString>,
) -> PathBuf {
    if cfg!(windows)
        && let Some(local_app_data) = local_app_data
    {
        return local_app_data.into();
    }

    if let Some(xdg_data_home) = xdg_data_home {
        return xdg_data_home.into();
    }

    if let Some(home) = home {
        return PathBuf::from(home).join(".local").join("share");
    }

    std::env::temp_dir()
}

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

    #[test]
    fn endpoint_file_path_uses_explicit_override() {
        let path = endpoint_file_path_from_env(Some(OsString::from("C:/tmp/alcom-endpoint.json")));
        assert_eq!(path, PathBuf::from("C:/tmp/alcom-endpoint.json"));
    }

    #[test]
    fn default_endpoint_file_lives_under_mcp_data_directory() {
        let path = default_endpoint_file_path(Path::new("/data"));
        assert_eq!(
            path,
            Path::new("/data")
                .join("VRChatCreatorCompanion")
                .join("mcp")
                .join("endpoint.json")
        );
    }

    #[test]
    fn response_serializes_camel_case_request_id_and_ok() {
        let request_id = Uuid::nil();
        let serialized = serde_json::to_value(IpcResponse::success(
            request_id,
            serde_json::json!({ "value": 1 }),
        ))
        .unwrap();
        assert_eq!(serialized["requestId"], request_id.to_string());
        assert_eq!(serialized["ok"], true);
        assert!(serialized.get("result").is_some());
    }
}
