// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! # Output Module
//!
//! Defines the output format for REX results.
//!
//! This module provides the [`RexOutput`] enum which represents all possible
//! outcomes from a REX execution. The output is designed to be:
//! - Self-describing with status tags
//! - Easily serializable to JSON
//! - Type-safe with exhaustive matching
//!
//! ## Output Types
//!
//! - `Success` - The REX execution completed successfully and returned data
//! - `RexError` - The REX execution encountered an error
//! - `UnserializableResponse` - The REX execution produced output that couldn't be serialized
//!
//! ## Serialization Format
//!
//! All outputs serialize to a consistent JSON structure:
//! ```json
//! {
//!   "status": "success|rex-error|unserializable-response",
//!   "contents": <actual data or error details>
//! }
//! ```

use std::{fmt, fmt::Debug};

use borsh::{BorshDeserialize, BorshSerialize};
use chrono::SecondsFormat;
use rialo_cli_representable::Representable;
use serde::{Deserialize, Serialize};

use crate::RexError;

/// Result for filtering operations.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub enum FilterResult {
    Success(Vec<u8>),
    Error(String),
}

impl FilterResult {
    /// Returns true if the filter result is a success.
    pub fn is_success(&self) -> bool {
        matches!(self, FilterResult::Success(_))
    }

    /// Returns true if the filter result is an error.
    pub fn is_error(&self) -> bool {
        matches!(self, FilterResult::Error(_))
    }

    /// Returns the contained bytes if the filter result is a success, otherwise returns `None`.
    pub fn as_success(&self) -> Option<&Vec<u8>> {
        if let FilterResult::Success(ref bytes) = self {
            Some(bytes)
        } else {
            None
        }
    }

    /// Returns the contained error message if the filter result is an error, otherwise returns `None`.
    pub fn as_error(&self) -> Option<&String> {
        if let FilterResult::Error(ref err) = self {
            Some(err)
        } else {
            None
        }
    }
}

impl fmt::Display for FilterResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FilterResult::Success(bytes) => write!(f, "Success: {:?}", bytes),
            FilterResult::Error(err) => write!(f, "Error: {}", err),
        }
    }
}

impl From<Result<Vec<u8>, String>> for FilterResult {
    fn from(result: Result<Vec<u8>, String>) -> Self {
        match result {
            Ok(bytes) => FilterResult::Success(bytes),
            Err(err) => FilterResult::Error(err),
        }
    }
}

/// Represents the data returned by REX.
/// This is a vector of bytes that can contain any data returned by the REX execution.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub enum RexData {
    /// The raw bytes returned by the REX execution.
    Raw(Vec<u8>),
    /// The filtered bytes returned by the REX execution.
    Filtered(Vec<FilterResult>),
}

impl RexData {
    /// Returns true if the REX data is raw.
    pub fn is_raw(&self) -> bool {
        matches!(self, RexData::Raw(_))
    }

    /// Returns true if the REX data is filtered.
    pub fn is_filtered(&self) -> bool {
        matches!(self, RexData::Filtered(_))
    }

    /// Returns the contained raw bytes if the REX data is raw, otherwise returns `None`.
    pub fn as_raw(&self) -> Option<&Vec<u8>> {
        if let RexData::Raw(ref data) = self {
            Some(data)
        } else {
            None
        }
    }

    /// Returns the contained filtered results if the REX data is filtered, otherwise returns `None`.
    pub fn as_filtered(&self) -> Option<&Vec<FilterResult>> {
        if let RexData::Filtered(ref results) = self {
            Some(results)
        } else {
            None
        }
    }

    /// Returns the length of the contained data in bytes.
    pub fn len(&self) -> usize {
        match self {
            RexData::Raw(data) => data.len(),
            RexData::Filtered(results) => results
                .iter()
                .map(|r| match r {
                    FilterResult::Success(bytes) => bytes.len(),
                    FilterResult::Error(err) => err.len(),
                })
                .sum(),
        }
    }

    /// Returns true if the contained data is empty.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

/// The response received from REX.
///
/// This data structure is used to represent the successful output of a REX request.
#[derive(
    Clone,
    Debug,
    Eq,
    PartialEq,
    Serialize,
    Deserialize,
    BorshSerialize,
    BorshDeserialize,
    Representable,
)]
#[representable(human_readable = "rex_response_human_readable")]
pub struct RexResponse {
    /// The raw response data from the REX execution, encoded as bytes.
    pub response: RexData,
    /// The timestamp when the REX response was generated.
    pub timestamp: String,
}

fn rex_response_human_readable(response: &RexResponse) -> String {
    let mut out = String::new();
    out.push_str(&format!(
        "Response: {:?}, Timestamp (from TEE): {}",
        response.response, response.timestamp
    ));
    out
}

impl RexResponse {
    /// Creates a new `RexResponse` with the given response data and timestamp.
    pub fn new(response: RexData) -> Self {
        Self {
            response,
            // Always uses 'Z' suffix and 6 decimal places (microseconds)
            // Produces exactly 30 bytes: "2025-08-24T10:59:40.123456Z"
            timestamp: chrono::Utc::now().to_rfc3339_opts(SecondsFormat::Micros, true),
        }
    }

    /// Convert to successful response data. In case of `RexData::Raw` returns a vector of one
    /// element containing the raw bytes. In case of `RexData::Filtered` returns a vector of all
    /// successful filtered bytes.
    ///
    /// If there are no successful responses, returns an empty vector.
    pub fn into_successful_responses(self) -> Vec<Vec<u8>> {
        match self.response {
            RexData::Raw(data) => vec![data],
            RexData::Filtered(results) => results
                .into_iter()
                .filter_map(|r| {
                    if let FilterResult::Success(bytes) = r {
                        Some(bytes)
                    } else {
                        None
                    }
                })
                .collect(),
        }
    }
}

/// The output from REX.
///
/// This enum represents the possible outcomes of a REX execution.
#[derive(
    Clone,
    Debug,
    Eq,
    PartialEq,
    Serialize,
    Deserialize,
    BorshSerialize,
    BorshDeserialize,
    Representable,
)]
#[serde(tag = "status", content = "contents")]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
#[representable(human_readable = "rex_output_human_readable")]
pub enum RexOutput {
    /// The response of a successfully handled REX request.
    Success(RexResponse),
    /// The details of an error reported by a REX execution.
    RexError(RexError),
    /// The REX execution produced an unserializable response.
    UnserializableResponse(String),
}

fn rex_output_human_readable(output: &RexOutput) -> String {
    match output {
        RexOutput::Success(resp) => {
            let len = resp.response.len();
            format!("Success: {} bytes @ {}", len, resp.timestamp)
        }
        RexOutput::RexError(err) => format!("RexError: {}", err),
        RexOutput::UnserializableResponse(msg) => {
            format!("UnserializableResponse: {}", msg)
        }
    }
}

impl RexOutput {
    /// Returns true if the output is a success.
    pub fn is_success(&self) -> bool {
        matches!(self, RexOutput::Success(_))
    }

    /// Returns true if the output is a REX error.
    pub fn is_rex_error(&self) -> bool {
        matches!(self, RexOutput::RexError(_))
    }

    /// Returns true if the output is an unserializable response.
    pub fn is_unserializable_response(&self) -> bool {
        matches!(self, RexOutput::UnserializableResponse(_))
    }

    /// Returns the contained `RexResponse` if the output is a success, otherwise returns `None`.
    pub fn as_success(&self) -> Option<&RexResponse> {
        if let RexOutput::Success(ref response) = self {
            Some(response)
        } else {
            None
        }
    }

    /// Returns the contained `RexError` if the output is a REX error, otherwise returns `None`.
    pub fn as_rex_error(&self) -> Option<&RexError> {
        if let RexOutput::RexError(ref error) = self {
            Some(error)
        } else {
            None
        }
    }

    /// Converts to successful response data if the output is a success, and we have successful `RexData`.
    /// Otherwise, returns `None`.
    pub fn into_success_payload(self) -> Option<Vec<Vec<u8>>> {
        match self {
            RexOutput::Success(response) => Some(response.into_successful_responses()),
            _ => None,
        }
    }
}

impl From<Result<RexResponse, RexError>> for RexOutput {
    fn from(result: Result<RexResponse, RexError>) -> Self {
        match result {
            Ok(response) => RexOutput::Success(response),
            Err(error) => RexOutput::RexError(error),
        }
    }
}

impl From<RexError> for RexOutput {
    fn from(error: RexError) -> Self {
        RexOutput::RexError(error)
    }
}

#[cfg(test)]
mod test {
    use serde_json::json;

    use super::*;

    #[test]
    fn test_rex_result_success_serialization() {
        let rex_data = RexResponse::new(RexData::Raw(
            serde_json::to_vec(&json!({"data": 42, "message": "test"})).unwrap(),
        ));
        let result = RexOutput::Success(rex_data.clone());
        let json = serde_json::to_value(&result).unwrap();

        assert_eq!(
            json,
            json!({
                "status": "success",
                "contents": rex_data
            })
        );
    }

    #[test]
    fn test_rex_result_error_serialization() {
        let error = RexError::HttpStatusError {
            status: 404,
            reason: String::from("HTTP Status Code 404 Not Found"),
        };

        let result = RexOutput::RexError(error.clone());
        let json = serde_json::to_value(&result).unwrap();

        assert_eq!(
            json,
            json!({
                "status": "rex-error",
                "contents": error
            })
        );
    }

    #[test]
    fn test_rex_result_unserializable_serialization() {
        let result = RexOutput::UnserializableResponse("Failed to parse response".to_string());
        let json = serde_json::to_value(&result).unwrap();

        assert_eq!(
            json,
            json!({
                "status": "unserializable-response",
                "contents": "Failed to parse response"
            })
        );
    }

    #[test]
    fn test_rex_result_deserialization() {
        // Test deserializing success
        let expected = RexResponse::new(RexData::Raw(
            serde_json::to_vec(&json!({"data": 123 })).unwrap(),
        ));
        let expected_clone = expected.clone();
        let json = json!({
            "status": "success",
            "contents": expected_clone
        });
        let result: RexOutput = serde_json::from_value(json).unwrap();
        assert_eq!(result, RexOutput::Success(expected_clone));

        // Test deserializing error
        let error = RexError::HttpStatusError {
            status: 500,
            reason: "Internal Server Error".to_string(),
        };
        let json = json!({
            "status": "rex-error",
            "contents": error.clone()
        });
        let result: RexOutput = serde_json::from_value(json).unwrap();
        assert_eq!(result, RexOutput::RexError(error));

        // Test deserializing unserializable response
        let json = json!({
            "status": "unserializable-response",
            "contents": "Parse error"
        });
        let result: RexOutput = serde_json::from_value(json).unwrap();
        assert_eq!(
            result,
            RexOutput::UnserializableResponse("Parse error".to_string())
        );
    }

    #[test]
    fn test_rex_result_roundtrip() {
        let rex_response = RexResponse::new(RexData::Raw(
            serde_json::to_vec(&json!({"key": "value", "number": 42})).unwrap(),
        ));
        let error = RexError::HttpStatusError {
            status: 403,
            reason: "Forbidden".to_string(),
        };
        let test_cases = vec![
            RexOutput::Success(rex_response),
            RexOutput::RexError(error),
            RexOutput::UnserializableResponse("Invalid JSON".to_string()),
        ];

        for original in test_cases {
            let serialized = serde_json::to_string(&original).unwrap();
            let deserialized: RexOutput = serde_json::from_str(&serialized).unwrap();
            assert_eq!(original, deserialized);
        }
    }

    #[test]
    fn test_rex_result_complex_nested_values() {
        let complex_value = json!({
            "nested": {
                "array": [1, 2, 3],
                "object": {"key": "value"},
                "null": null,
                "bool": true
            }
        });
        let rex_response =
            RexResponse::new(RexData::Raw(serde_json::to_vec(&complex_value).unwrap()));

        let result = RexOutput::Success(rex_response.clone());
        let json = serde_json::to_value(&result).unwrap();

        assert_eq!(json["status"], "success");
        assert_eq!(json["contents"], json!(rex_response));
    }

    #[test]
    fn test_rex_result_empty_values() {
        // Test with empty object
        let rex_response = RexResponse::new(RexData::Raw(serde_json::to_vec(&json!({})).unwrap()));
        let result = RexOutput::Success(rex_response.clone());
        let json = serde_json::to_value(&result).unwrap();
        assert_eq!(
            json,
            json!({
                "status": "success",
                "contents": rex_response
            })
        );

        // Test with empty string
        let result = RexOutput::UnserializableResponse("".to_string());
        let json = serde_json::to_value(&result).unwrap();
        assert_eq!(
            json,
            json!({
                "status": "unserializable-response",
                "contents": ""
            })
        );
    }
}
