//! Response envelope types for API responses.

#[cfg(feature = "coded-error")]
use cruxi::CodedError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;

/// Generic API response envelope.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Response<T> {
    /// The primary data payload.
    pub data: Vec<T>,
    /// Error information if the request failed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub errors: Option<ApiError>,
    /// Metadata about the response (pagination, etc.).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub meta: Option<Meta>,
    /// Included related resources.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub includes: Option<Includes>,
}

impl<T> Response<T> {
    /// Creates a successful response with data.
    #[must_use]
    pub fn success(data: Vec<T>) -> Self {
        Self {
            data,
            errors: None,
            meta: None,
            includes: None,
        }
    }

    /// Creates a successful response with a single item.
    #[must_use]
    pub fn single(item: T) -> Self {
        Self::success(vec![item])
    }

    /// Creates an error response.
    #[must_use]
    pub fn error(error: ApiError) -> Self {
        Self {
            data: Vec::new(),
            errors: Some(error),
            meta: None,
            includes: None,
        }
    }

    /// Adds metadata to the response.
    #[must_use]
    pub fn with_meta(mut self, meta: Meta) -> Self {
        self.meta = Some(meta);
        self
    }

    /// Adds included resources to the response.
    #[must_use]
    pub fn with_includes(mut self, includes: Includes) -> Self {
        self.includes = Some(includes);
        self
    }
}

impl<T> Default for Response<T> {
    fn default() -> Self {
        Self {
            data: Vec::new(),
            errors: None,
            meta: None,
            includes: None,
        }
    }
}

/// API error information.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ApiError {
    /// Machine-readable error code.
    pub code: String,
    /// Unique identifier for this error instance.
    pub instance: Uuid,
    /// Human-readable title.
    pub title: String,
    /// Detailed explanation of the error.
    pub reason: String,
    /// Chain of underlying causes.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub root_causes: Vec<ErrorCause>,
}

impl ApiError {
    /// Creates a new API error with the given details.
    ///
    /// The `instance` is generated as a UUIDv7 by default.
    #[must_use]
    pub fn new(
        code: impl Into<String>,
        title: impl Into<String>,
        reason: impl Into<String>,
    ) -> Self {
        Self {
            code: code.into(),
            instance: Uuid::now_v7(),
            title: title.into(),
            reason: reason.into(),
            root_causes: Vec::new(),
        }
    }

    /// Sets the error instance identifier.
    #[must_use]
    pub fn with_instance(mut self, instance: Uuid) -> Self {
        self.instance = instance;
        self
    }

    /// Sets the human-readable title.
    #[must_use]
    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }

    /// Sets the detailed reason for the error.
    #[must_use]
    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
        self.reason = reason.into();
        self
    }

    /// Adds a root cause to the error chain.
    #[must_use]
    pub fn with_cause(mut self, cause: ErrorCause) -> Self {
        self.root_causes.push(cause);
        self
    }

    /// Adds multiple root causes to the error chain.
    #[must_use]
    pub fn with_causes(mut self, causes: impl IntoIterator<Item = ErrorCause>) -> Self {
        self.root_causes.extend(causes);
        self
    }
}

#[cfg(feature = "coded-error")]
impl From<&CodedError> for ApiError {
    fn from(value: &CodedError) -> Self {
        Self {
            code: value.code().to_string(),
            instance: parse_instance_or_new(value.instance()),
            title: value
                .title()
                .map(std::borrow::ToOwned::to_owned)
                .unwrap_or_else(|| "An Error Occurred".to_string()),
            reason: value
                .reason()
                .map(std::borrow::ToOwned::to_owned)
                .unwrap_or_else(|| value.to_string()),
            root_causes: coded_error_root_causes(value),
        }
    }
}

#[cfg(feature = "coded-error")]
impl From<CodedError> for ApiError {
    fn from(value: CodedError) -> Self {
        Self::from(&value)
    }
}

/// An underlying cause in an error chain.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ErrorCause {
    /// Machine-readable cause code.
    pub code: String,
    /// Unique identifier for this cause instance.
    pub instance: Uuid,
    /// Human-readable title.
    pub title: String,
    /// Detailed explanation of the cause.
    pub reason: String,
}

impl ErrorCause {
    /// Creates a new error cause.
    ///
    /// The `instance` is generated as a UUIDv7 by default.
    #[must_use]
    pub fn new(
        code: impl Into<String>,
        title: impl Into<String>,
        reason: impl Into<String>,
    ) -> Self {
        Self {
            code: code.into(),
            instance: Uuid::now_v7(),
            title: title.into(),
            reason: reason.into(),
        }
    }

    /// Sets the cause instance identifier.
    #[must_use]
    pub fn with_instance(mut self, instance: Uuid) -> Self {
        self.instance = instance;
        self
    }

    /// Sets the human-readable title for this cause.
    #[must_use]
    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }

    /// Sets the detailed reason for this cause.
    #[must_use]
    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
        self.reason = reason.into();
        self
    }
}

#[cfg(feature = "coded-error")]
impl From<&CodedError> for ErrorCause {
    fn from(value: &CodedError) -> Self {
        Self {
            code: value.code().to_string(),
            instance: parse_instance_or_new(value.instance()),
            title: value
                .title()
                .map(std::borrow::ToOwned::to_owned)
                .unwrap_or_else(|| "An Error Occurred".to_string()),
            reason: value
                .reason()
                .map(std::borrow::ToOwned::to_owned)
                .unwrap_or_else(|| value.to_string()),
        }
    }
}

#[cfg(feature = "coded-error")]
impl From<CodedError> for ErrorCause {
    fn from(value: CodedError) -> Self {
        Self::from(&value)
    }
}

#[cfg(feature = "coded-error")]
fn parse_instance_or_new(instance: Option<&str>) -> Uuid {
    instance
        .and_then(|raw| Uuid::parse_str(raw).ok())
        .unwrap_or_else(Uuid::now_v7)
}

#[cfg(feature = "coded-error")]
fn coded_error_root_causes(value: &CodedError) -> Vec<ErrorCause> {
    let mut causes = Vec::new();
    let mut current = std::error::Error::source(value);

    while let Some(source) = current {
        if let Some(coded) = source.downcast_ref::<CodedError>() {
            causes.push(ErrorCause::from(coded));
            current = std::error::Error::source(coded);
        } else {
            break;
        }
    }

    causes
}

/// Response metadata.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Meta {
    /// Additional key-value metadata.
    #[serde(flatten)]
    pub extra: HashMap<String, serde_json::Value>,
}

impl Meta {
    /// Creates empty metadata.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Inserts a key-value pair into the metadata.
    #[must_use]
    pub fn with_entry(
        mut self,
        key: impl Into<String>,
        value: impl Into<serde_json::Value>,
    ) -> Self {
        self.extra.insert(key.into(), value.into());
        self
    }

    /// Gets a value from the metadata.
    #[must_use]
    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
        self.extra.get(key)
    }
}

/// Included related resources.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Includes {
    /// Map of resource type to list of included resources.
    #[serde(flatten)]
    pub resources: HashMap<String, Vec<serde_json::Value>>,
}

impl Includes {
    /// Creates empty includes.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Adds included resources of a given type.
    #[must_use]
    pub fn with_resources(
        mut self,
        resource_type: impl Into<String>,
        resources: Vec<serde_json::Value>,
    ) -> Self {
        self.resources.insert(resource_type.into(), resources);
        self
    }

    /// Gets included resources of a given type.
    #[must_use]
    pub fn get(&self, resource_type: &str) -> Option<&Vec<serde_json::Value>> {
        self.resources.get(resource_type)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[cfg(feature = "coded-error")]
    use cruxi::CodedError;

    #[test]
    fn test_response_success() {
        let response: Response<i32> = Response::success(vec![1, 2, 3]);
        assert_eq!(response.data, vec![1, 2, 3]);
        assert!(response.errors.is_none());
    }

    #[test]
    fn test_response_single() {
        let response: Response<&str> = Response::single("hello");
        assert_eq!(response.data, vec!["hello"]);
    }

    #[test]
    fn test_response_error() {
        let error = ApiError::new(
            "NOT_FOUND",
            "Resource not found",
            "The resource was not found",
        );
        let response: Response<()> = Response::error(error);
        assert!(response.data.is_empty());
        assert!(response.errors.is_some());
        let err = response.errors.as_ref().expect("error should be present");
        assert_eq!(err.code, "NOT_FOUND");
    }

    #[test]
    fn test_api_error_builder() {
        let instance = Uuid::now_v7();
        let error = ApiError::new(
            "VALIDATION_ERROR",
            "Validation failed",
            "Field 'name' is required",
        )
        .with_title("Validation failed")
        .with_reason("Field 'name' is required")
        .with_instance(instance)
        .with_cause(ErrorCause::new(
            "REQUIRED_FIELD",
            "Name is required",
            "name is required",
        ));

        assert_eq!(error.code, "VALIDATION_ERROR");
        assert_eq!(error.title, "Validation failed".to_string());
        assert_eq!(error.reason, "Field 'name' is required".to_string());
        assert_eq!(error.instance, instance);
        assert_eq!(error.root_causes.len(), 1);
        assert_eq!(error.root_causes[0].code, "REQUIRED_FIELD");
    }

    #[test]
    fn test_api_error_serializes_root_causes_as_camel_case() {
        let error =
            ApiError::new("VALIDATION_ERROR", "Validation failed", "validation failed").with_cause(
                ErrorCause::new("REQUIRED_FIELD", "Name is required", "name is required"),
            );
        let response: Response<()> = Response::error(error);

        let json = serde_json::to_string(&response).expect("serialization should succeed");
        assert!(json.contains("rootCauses"));
    }

    #[cfg(feature = "coded-error")]
    #[test]
    fn test_api_error_from_coded_error_preserves_fields() {
        let instance = Uuid::now_v7();
        let coded = CodedError::new("USER-CREATE-000409")
            .with_instance(instance.to_string())
            .with_title("Conflict")
            .with_reason("A user with this email already exists");

        let api = ApiError::from(&coded);
        assert_eq!(api.code, "USER-CREATE-000409");
        assert_eq!(api.instance, instance);
        assert_eq!(api.title, "Conflict");
        assert_eq!(api.reason, "A user with this email already exists");
        assert!(api.root_causes.is_empty());
    }

    #[cfg(feature = "coded-error")]
    #[test]
    fn test_api_error_from_coded_error_maps_source_chain_to_root_causes() {
        let leaf = CodedError::new("USER-CREATE-000001")
            .with_title("Name is required")
            .with_reason("Name is required");
        let mid = CodedError::new("USER-CREATE-000002")
            .with_title("Underaged users not allowed")
            .with_reason("A user must be over the age of 13")
            .with_source(leaf);
        let root = CodedError::new("USER-CREATE-000000")
            .with_title("An Error Occurred")
            .with_reason("There was a failure providing the required information")
            .with_source(mid);

        let api = ApiError::from(root);
        assert_eq!(api.code, "USER-CREATE-000000");
        assert_eq!(api.root_causes.len(), 2);
        assert_eq!(api.root_causes[0].code, "USER-CREATE-000002");
        assert_eq!(api.root_causes[1].code, "USER-CREATE-000001");
    }

    #[cfg(feature = "coded-error")]
    #[test]
    fn test_api_error_from_coded_error_generates_uuid_when_instance_is_invalid() {
        let coded = CodedError::new("USER-CREATE-000500").with_instance("not-a-uuid");

        let api = ApiError::from(coded);
        assert_ne!(api.instance, Uuid::nil());
    }

    #[cfg(feature = "coded-error")]
    #[test]
    fn test_api_error_from_coded_error_uses_defaults_when_title_and_reason_missing() {
        let coded = CodedError::new("USER-CREATE-000500");

        let api = ApiError::from(coded);
        assert_eq!(api.code, "USER-CREATE-000500");
        assert_eq!(api.title, "An Error Occurred");
        assert_eq!(api.reason, "cruxi: coded error [USER-CREATE-000500]");
        assert!(api.root_causes.is_empty());
    }

    #[cfg(feature = "coded-error")]
    #[test]
    fn test_api_error_from_coded_error_uses_display_for_reason_when_reason_missing() {
        let coded = CodedError::new("USER-CREATE-000400").with_title("Validation failed");

        let api = ApiError::from(coded);
        assert_eq!(api.code, "USER-CREATE-000400");
        assert_eq!(api.title, "Validation failed");
        assert_eq!(api.reason, "Validation failed");
    }

    #[test]
    fn test_meta_operations() {
        let meta = Meta::new().with_entry("total", 100).with_entry("page", 1);

        assert_eq!(
            meta.get("total"),
            Some(&serde_json::Value::Number(100.into()))
        );
        assert_eq!(meta.get("page"), Some(&serde_json::Value::Number(1.into())));
        assert!(meta.get("missing").is_none());
    }

    #[test]
    fn test_includes_operations() {
        let includes = Includes::new()
            .with_resources("users", vec![serde_json::json!({"id": 1, "name": "Alice"})]);

        let users = includes.get("users").expect("users should be present");
        assert_eq!(users.len(), 1);
        assert!(includes.get("posts").is_none());
    }

    #[test]
    fn test_response_serialization() {
        let response: Response<i32> =
            Response::success(vec![1, 2, 3]).with_meta(Meta::new().with_entry("total", 3));

        let json = serde_json::to_string(&response).expect("serialization should succeed");
        assert!(json.contains("\"data\":[1,2,3]"));
        assert!(json.contains("\"total\":3"));
    }

    #[test]
    fn test_response_deserialization() {
        let json = r#"{"data":[1,2,3],"meta":{"total":3}}"#;
        let response: Response<i32> =
            serde_json::from_str(json).expect("deserialization should succeed");

        assert_eq!(response.data, vec![1, 2, 3]);
        assert!(response.meta.is_some());
    }
}
