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

use rialo_cli_representable::Representable;
use rialo_cli_representation::HumanReadable;
use rialo_s_pubkey::Pubkey;
use rialo_types::{OracleData, OracleId, OracleOutput};
use serde::{Deserialize, Serialize};

use crate::errors::OracleProcessorError;

/// Oracle update structure
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Representable)]
#[representable(human_readable = "oracle_update_human_readable")]
pub struct OracleUpdate {
    /// The output that resulted from the Oracle Duty being performed, serialized as bytes with Borsh.
    pub(crate) data: Vec<u8>,
    /// The public key of the validator
    pub(crate) validator: Pubkey,
}
fn oracle_update_human_readable(update: &OracleUpdate) -> String {
    let mut out = String::new();
    out.push_str(&format!("Validator: {:?}; ", update.validator));
    out.push_str("Data: ");
    // Try to decode the oracle output
    match update.try_data_as_output() {
        Ok(oracle_output) => {
            out.push_str(&format!("{oracle_output:?}"));
        }
        Err(_) => {
            out.push_str("<Unable to decode>: \n");
        }
    }
    out
}

impl OracleUpdate {
    pub fn new(data: Vec<u8>, validator: Pubkey) -> Self {
        Self { data, validator }
    }

    /// Returns the Oracle data as a Base64 encoded string
    ///
    /// # Returns
    ///
    /// The output that resulted from the Oracle Duty being performed, serialized as bytes with Borsh
    pub fn data(&self) -> &[u8] {
        &self.data
    }

    /// The public key of the validator.
    pub fn validator(&self) -> Pubkey {
        self.validator
    }

    /// Returns the data as [`OracleOutput`]
    ///
    /// # Returns
    ///
    /// The output that resulted from the Oracle Duty being performed, deserialized as [`OracleOutput`]
    pub fn try_data_as_output(&self) -> Result<OracleOutput, OracleProcessorError> {
        borsh::from_slice(&self.data).map_err(|_| OracleProcessorError::ReportData)
    }

    /// Returns the `OracleData`  payload returned by the oracle if the request was a success.
    /// Otherwise, returns None.
    ///
    /// # Returns
    ///
    /// An Option containing the `OracleData`  payload if the oracle output was a success, or None otherwise.
    pub fn success_payload(&self) -> Option<OracleData> {
        match self.try_data_as_output() {
            Ok(OracleOutput::Success(response)) => Some(response.response),
            _ => None,
        }
    }

    pub fn get_oracle_response_timestamp(&self) -> Option<String> {
        if let Ok(OracleOutput::Success(response)) = self.try_data_as_output() {
            Some(response.timestamp)
        } else {
            None
        }
    }
}

/// Represents an oracle's definition and configuration
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Representable)]
#[representable(human_readable = "oracle_report_human_readable")]
pub struct OracleReport {
    pub oracle_id: OracleId,        // Unique identifier
    pub round: u64,                 // Round that received the report
    pub updates: Vec<OracleUpdate>, // Updates from validator for this oracle
}

fn oracle_report_human_readable(report: &OracleReport) -> String {
    let mut out = String::new();

    out.push_str("Oracle Report\n");
    out.push_str("=============\n\n");
    out.push_str(&format!("Oracle ID: {}\n", report.oracle_id));
    out.push_str(&format!("Round: {}\n", report.round));
    out.push_str(&format!("Number of Updates: {}\n", report.updates.len()));

    if !report.updates.is_empty() {
        out.push_str("\nUpdates:\n");
        for (i, update) in report.updates.iter().enumerate() {
            out.push_str(&format!("  Update {}:\n", i + 1));
            out.push_str(&format!("    Validator: {}\n", update.validator()));
            out.push_str(&format!("    Data Size: {} bytes\n", update.data().len()));

            // Show success payload if available
            if let Ok(output) = update.try_data_as_output() {
                match output {
                    OracleOutput::Success(response) => {
                        out.push_str(&format!("    Success: {:?}\n", response.human_readable()));
                    }
                    OracleOutput::OracleError(err) => {
                        out.push_str(&format!("    OracleError: {err:?}\n"));
                    }
                    OracleOutput::UnserializableResponse(err) => {
                        out.push_str(&format!("    UnserializableResponse: {err:?}\n"));
                    }
                    _ => {
                        out.push_str(&format!("    Unknown Output Type: {:?}\n", output));
                    }
                }
            } else {
                out.push_str(&format!("    Output: {}\n", update.human_readable()));
            }
        }
    }

    out
}

impl OracleReport {
    /// Converts the `OracleReport` into a `Vec<OracleUpdate>`
    ///
    /// This function is used to extract the updates from the `OracleReport`
    ///
    /// # Returns
    ///
    /// A vector of `OracleUpdate` instances
    pub fn into_updates(self) -> Vec<OracleUpdate> {
        self.updates
    }

    /// Returns an iterator of tuples containing the validator Pubkey and `OracleOutput`
    pub fn outputs_with_validators(&self) -> impl Iterator<Item = (Pubkey, OracleOutput)> + '_ {
        self.updates
            .iter()
            .filter_map(|update| match update.try_data_as_output() {
                Ok(output) => Some((update.validator, output)),
                Err(_) => None,
            })
    }

    /// Returns an iterator over containing all the `OracleOutput`
    ///
    /// # Returns
    ///
    /// Returns an iterator over all the `OracleOutput`s.
    pub fn outputs(&self) -> impl Iterator<Item = OracleOutput> + '_ {
        self.updates
            .iter()
            .filter_map(|update| update.try_data_as_output().ok())
    }

    /// Returns an iterator over the validators
    ///
    /// # Returns
    ///
    /// An iterator over the validators' Pubkeys
    pub fn validators(&self) -> impl Iterator<Item = Pubkey> + '_ {
        self.updates.iter().map(|update| update.validator)
    }
}

/// Extracts the round from the bincode data
///
/// # Arguments
///
/// * `bincode_data` - A byte slice containing the bincode data
///
/// # Returns
///
/// The extracted round value
///
/// # Errors
///
/// Returns an error if the data is invalid or if the conversion fails
pub fn extract_round_raw(bincode_data: &[u8]) -> Result<u64, OracleProcessorError> {
    // The OracleReport structure has changed, so we need to deserialize it properly
    // to extract the round field
    let report: OracleReport =
        bincode::deserialize(bincode_data).map_err(|_| OracleProcessorError::ReportData)?;

    Ok(report.round)
}
