use async_trait::async_trait;
use anyhow::Result;
use serde_json::Value;
use serde::{Serialize, Deserialize};
use std::collections::HashMap;

/// Context provided to the node execution
pub struct NodeContext {
    pub request_id: String,
    pub base_commit: String,
    pub root_path: String,
}

impl NodeContext {
    pub fn new(request_id: String, base_commit: String, root_path: String) -> Self {
        Self {
            request_id,
            base_commit,
            root_path,
        }
    }
}

/// Content of an artifact
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ArtifactContent {
    Json(Value),
    Text(String),
    Bytes(Vec<u8>),
}

impl From<Value> for ArtifactContent {
    fn from(v: Value) -> Self {
        ArtifactContent::Json(v)
    }
}

impl From<String> for ArtifactContent {
    fn from(s: String) -> Self {
        ArtifactContent::Text(s)
    }
}

impl ArtifactContent {
    pub fn as_bytes(&self) -> Result<Vec<u8>> {
        match self {
            ArtifactContent::Json(v) => Ok(serde_json::to_vec_pretty(v)?),
            ArtifactContent::Text(s) => Ok(s.as_bytes().to_vec()),
            ArtifactContent::Bytes(b) => Ok(b.clone()),
        }
    }
}

/// Result of the node execution
pub struct NodeExecutionResult {
    /// Artifacts to be saved to VGCS.
    /// Key: Relative file path (e.g., "profile.json")
    /// Value: Content
    pub artifacts: HashMap<String, ArtifactContent>,
    
    /// Metadata summary for the task result event
    pub meta_summary: Option<Value>,
}

/// New Type for Cache Key to avoid string confusion
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CacheKey(pub String);

impl std::fmt::Display for CacheKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[async_trait]
pub trait WorkflowNode: Send + Sync {
    /// Unique identifier/type of the node (e.g., "yfinance", "analysis")
    fn node_type(&self) -> &str;

    /// Cache Configuration Interface
    /// 
    /// Returns `None` (default) to bypass cache.
    /// Returns `Some((CacheKey, Duration))` to enable caching.
    fn get_cache_config(&self, _config: &Value) -> Option<(CacheKey, std::time::Duration)> {
        None
    }

    /// Core execution logic
    /// 
    /// # Arguments
    /// * `context` - Context including request_id, base_commit, etc.
    /// * `config` - Task configuration (e.g., symbol, market)
    async fn execute(
        &self, 
        context: &NodeContext, 
        config: &Value
    ) -> Result<NodeExecutionResult>;

    /// Render the report from the execution result
    /// 
    /// This method is synchronous as data should be available in the result.
    fn render_report(
        &self, 
        result: &NodeExecutionResult
    ) -> Result<String>;
}

