use polyform_runtime::{CallTelemetry, Client as RuntimeClient};

use crate::{Composition, Result, ZipError};

pub use polyform_runtime::TelemetryEvent;

/// ZIP-specific adapter around the reusable Polyform runtime client.
pub struct Client {
    inner: RuntimeClient,
    pub client_id: String,
    pub release_id: String,
    pub revision: u64,
    pub composition: Composition,
}

impl Client {
    pub fn register(
        base_url: &str,
        release_id: u64,
        installation_id: Option<&str>,
        strategy: &str,
    ) -> Result<Self> {
        let inner = RuntimeClient::register(base_url, release_id, installation_id, strategy)
            .map_err(runtime_error)?;
        Self::from_inner(inner)
    }

    fn from_inner(inner: RuntimeClient) -> Result<Self> {
        let composition = Composition {
            implementations: inner.composition.implementations.clone(),
            composition_id: None,
        };
        composition.validate()?;
        Ok(Self {
            client_id: inner.client_id.clone(),
            release_id: inner.release_id.clone(),
            revision: inner.revision,
            composition,
            inner,
        })
    }

    pub fn refresh(&mut self) -> Result<()> {
        self.inner.refresh().map_err(runtime_error)?;
        self.client_id.clone_from(&self.inner.client_id);
        self.release_id.clone_from(&self.inner.release_id);
        self.revision = self.inner.revision;
        self.composition = Composition {
            implementations: self.inner.composition.implementations.clone(),
            composition_id: None,
        };
        self.composition.validate()
    }

    pub fn report_execution(
        &self,
        operation: &str,
        success: bool,
        duration_ms: f64,
        error_kind: Option<&str>,
        calls: &[String],
    ) -> Result<u64> {
        let calls = calls_for_event(calls, success);
        self.inner
            .report_execution(operation, success, duration_ms, error_kind, &calls)
            .map_err(runtime_error)
    }

    pub fn report_event(&self, event: &TelemetryEvent) -> Result<()> {
        self.inner.report_event(event).map_err(runtime_error)
    }
}

pub fn calls_for_event(calls: &[String], success: bool) -> Vec<CallTelemetry> {
    calls
        .iter()
        .rev()
        .take(30)
        .filter_map(|call| parse_call(call))
        .map(|(spec_function, implementation_id)| {
            CallTelemetry::new(
                spec_function,
                implementation_id,
                if success { "ok" } else { "error" },
            )
        })
        .collect()
}

fn parse_call(value: &str) -> Option<(String, String)> {
    value
        .split_once(':')
        .map(|(a, b)| (a.to_owned(), b.to_owned()))
}

fn runtime_error(error: polyform_runtime::RuntimeError) -> ZipError {
    ZipError::Http(error.to_string())
}

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

    #[test]
    fn telemetry_is_metadata_only_and_matches_shared_schema_shape() {
        let event = TelemetryEvent {
            client_id: "pc_12345678".into(),
            release_id: "1".into(),
            composition_revision: 2,
            event_type: "integrity_failure".into(),
            spec_function: "validate_crc".into(),
            implementation_id: "validate_crc.chunked".into(),
            message_code: Some("crc".into()),
            recent_calls: calls_for_event(&["validate_crc:validate_crc.chunked".into()], false),
        };
        let value = serde_json::to_value(event).unwrap();
        let object = value.as_object().unwrap();
        assert_eq!(object.len(), 8);
        assert!(object.contains_key("message_code"));
        assert!(!object.contains_key("message"));
        assert!(!object.contains_key("archive"));
        assert_eq!(value["recent_calls"][0]["outcome"], "error");
    }
}
