/*-------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 *
 * documentdb_gateway_core/src/responses/mod.rs
 *
 *-------------------------------------------------------------------------
 */

use bson::{rawdoc, Document, RawDocument};

use crate::{error::Result, protocol::OK_SUCCEEDED};

pub mod constant;
pub mod custom_error_mapping;
mod custom_pg_db_error;
mod error;
mod pg;
mod raw;
pub mod writer;

pub use custom_error_mapping::{
    global_custom_error_mapper, register_custom_error_mapper, CustomPostgresErrorMapper,
};
pub use custom_pg_db_error::CustomPgDbError;
pub(crate) use error::enhance_internal_error_message;
pub use error::error_to_raw_document_buf;
pub use pg::{
    from_known_external_error_code, i32_to_postgres_sqlstate, map_pg_db_error, map_pg_error,
    postgres_sqlstate_to_i32, PgResponse, PostgresErrorMappedResult,
};
pub use raw::RawResponse;

#[derive(Debug)]
pub enum Response {
    Raw(RawResponse),
    Pg(PgResponse),
}

impl Response {
    /// # Errors
    ///
    /// Returns an error if the operation fails.
    pub fn as_raw_document(&self) -> Result<&RawDocument> {
        match self {
            Self::Pg(pg) => pg.as_raw_document(),
            Self::Raw(raw) => Ok(raw.as_raw_document()),
        }
    }

    /// Returns the byte length of the response BSON document, or 0 if unavailable.
    #[must_use]
    pub fn response_byte_len(&self) -> usize {
        match self {
            Self::Pg(pg) => pg.response_byte_len(),
            Self::Raw(raw) => raw.response_byte_len(),
        }
    }

    /// # Errors
    /// Returns an error if the raw document cannot be converted to a JSON `Document`.
    pub fn as_json(&self) -> Result<Document> {
        Ok(Document::try_from(self.as_raw_document()?)?)
    }

    #[must_use]
    pub fn ok() -> Self {
        Self::Raw(RawResponse::new(rawdoc! {
            "ok":OK_SUCCEEDED,
        }))
    }

    /// Returns `true` if this response was produced by a write operation
    /// whose `PostgreSQL` UDF signalled failure (`p_success = false`) and
    /// the BSON contains `writeErrors`.
    #[must_use]
    pub const fn has_write_errors(&self) -> bool {
        match self {
            Self::Raw(raw) => raw.has_write_errors(),
            Self::Pg(_) => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use bson::rawdoc;

    use crate::responses::{raw::RawResponse, Response};

    #[test]
    fn raw_response_byte_len_matches_bson_bytes() {
        // An empty BSON document is exactly 5 bytes: 4-byte i32 size + 1 null terminator.
        let empty = rawdoc! {};
        assert_eq!(
            Response::Raw(RawResponse::new(empty)).response_byte_len(),
            5
        );

        // { "ok": 1.0 } = 4 (size) + [1 (type 0x01 double) + 3 ("ok\0") + 8 (f64)] + 1 (null) = 17
        let ok_response = Response::ok();
        assert_eq!(ok_response.response_byte_len(), 17);

        // Verify against an independent code path: serialize to bson::Document and
        // re-encode, so we aren't comparing as_bytes().len() against itself.
        let doc = rawdoc! { "value": "test" };
        let roundtripped: bson::Document =
            bson::from_slice(doc.as_bytes()).expect("valid BSON should deserialize");
        let mut re_encoded = Vec::new();
        roundtripped
            .to_writer(&mut re_encoded)
            .expect("re-encoding should succeed");
        assert_eq!(
            Response::Raw(RawResponse::new(doc)).response_byte_len(),
            re_encoded.len()
        );
    }
}
