use fabro_llm::error::SdkError;

/// Why a session was aborted.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AbortReason {
    WallClockTimeout,
    Cancelled,
}

impl std::fmt::Display for AbortReason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::WallClockTimeout => write!(f, "wall clock timeout"),
            Self::Cancelled => write!(f, "cancelled"),
        }
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, thiserror::Error)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum AgentError {
    #[error("LLM error: {0}")]
    Llm(#[from] SdkError),

    #[error("Session is closed")]
    SessionClosed,

    #[error("Invalid state: {0}")]
    InvalidState(String),

    #[error("Tool execution error: {0}")]
    ToolExecution(String),

    #[error("Aborted: {0}")]
    Aborted(AbortReason),
}

#[cfg(test)]
mod tests {
    use super::*;
    use fabro_llm::error::{ProviderErrorDetail, ProviderErrorKind};

    #[test]
    fn agent_error_from_sdk_error() {
        let sdk_err = SdkError::Network {
            message: "connection refused".into(),
        };
        let agent_err = AgentError::from(sdk_err);
        assert!(matches!(agent_err, AgentError::Llm(_)));
        assert!(agent_err.to_string().contains("connection refused"));
    }

    #[test]
    fn session_closed_display() {
        let err = AgentError::SessionClosed;
        assert_eq!(err.to_string(), "Session is closed");
    }

    #[test]
    fn invalid_state_display() {
        let err = AgentError::InvalidState("bad state".into());
        assert_eq!(err.to_string(), "Invalid state: bad state");
    }

    #[test]
    fn tool_execution_display() {
        let err = AgentError::ToolExecution("command failed".into());
        assert_eq!(err.to_string(), "Tool execution error: command failed");
    }

    #[test]
    fn aborted_display() {
        let err = AgentError::Aborted(AbortReason::Cancelled);
        assert_eq!(err.to_string(), "Aborted: cancelled");
    }

    #[test]
    fn aborted_wall_clock_timeout_display() {
        let err = AgentError::Aborted(AbortReason::WallClockTimeout);
        assert_eq!(err.to_string(), "Aborted: wall clock timeout");
    }

    // --- Serde roundtrip tests ---

    #[test]
    fn serde_roundtrip_llm_network() {
        let err = AgentError::Llm(SdkError::Network {
            message: "connection refused".into(),
        });
        let json = serde_json::to_string(&err).unwrap();
        let deserialized: AgentError = serde_json::from_str(&json).unwrap();
        assert_eq!(err.to_string(), deserialized.to_string());
    }

    #[test]
    fn serde_roundtrip_llm_provider() {
        let err = AgentError::Llm(SdkError::Provider {
            kind: ProviderErrorKind::RateLimit,
            detail: Box::new(ProviderErrorDetail {
                message: "too fast".into(),
                provider: "openai".into(),
                status_code: Some(429),
                error_code: None,
                retry_after: Some(2.0),
                raw: None,
            }),
        });
        let json = serde_json::to_string(&err).unwrap();
        let deserialized: AgentError = serde_json::from_str(&json).unwrap();
        assert_eq!(err.to_string(), deserialized.to_string());
    }

    #[test]
    fn serde_roundtrip_session_closed() {
        let err = AgentError::SessionClosed;
        let json = serde_json::to_string(&err).unwrap();
        let deserialized: AgentError = serde_json::from_str(&json).unwrap();
        assert_eq!(err.to_string(), deserialized.to_string());
    }

    #[test]
    fn serde_roundtrip_invalid_state() {
        let err = AgentError::InvalidState("bad".into());
        let json = serde_json::to_string(&err).unwrap();
        let deserialized: AgentError = serde_json::from_str(&json).unwrap();
        assert_eq!(err.to_string(), deserialized.to_string());
    }

    #[test]
    fn serde_roundtrip_tool_execution() {
        let err = AgentError::ToolExecution("cmd failed".into());
        let json = serde_json::to_string(&err).unwrap();
        let deserialized: AgentError = serde_json::from_str(&json).unwrap();
        assert_eq!(err.to_string(), deserialized.to_string());
    }

    #[test]
    fn serde_roundtrip_aborted() {
        let err = AgentError::Aborted(AbortReason::Cancelled);
        let json = serde_json::to_string(&err).unwrap();
        let deserialized: AgentError = serde_json::from_str(&json).unwrap();
        assert_eq!(err.to_string(), deserialized.to_string());
    }

    // --- Clone tests ---

    #[test]
    fn clone_all_variants() {
        let errors: Vec<AgentError> = vec![
            AgentError::Llm(SdkError::Network {
                message: "refused".into(),
            }),
            AgentError::SessionClosed,
            AgentError::InvalidState("reason".into()),
            AgentError::ToolExecution("reason".into()),
            AgentError::Aborted(AbortReason::Cancelled),
        ];
        for err in &errors {
            assert_eq!(err.to_string(), err.clone().to_string());
        }
    }

    // --- Serde tag format tests ---

    #[test]
    fn serde_tag_format_llm() {
        let err = AgentError::Llm(SdkError::Network {
            message: "refused".into(),
        });
        let json = serde_json::to_string(&err).unwrap();
        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
        assert_eq!(v["type"], "llm");
    }

    #[test]
    fn serde_tag_format_session_closed() {
        let err = AgentError::SessionClosed;
        let json = serde_json::to_string(&err).unwrap();
        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
        assert_eq!(v["type"], "session_closed");
    }

    #[test]
    fn serde_tag_format_invalid_state() {
        let err = AgentError::InvalidState("x".into());
        let json = serde_json::to_string(&err).unwrap();
        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
        assert_eq!(v["type"], "invalid_state");
    }

    #[test]
    fn serde_tag_format_tool_execution() {
        let err = AgentError::ToolExecution("x".into());
        let json = serde_json::to_string(&err).unwrap();
        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
        assert_eq!(v["type"], "tool_execution");
    }

    #[test]
    fn serde_tag_format_aborted() {
        let err = AgentError::Aborted(AbortReason::WallClockTimeout);
        let json = serde_json::to_string(&err).unwrap();
        let v: serde_json::Value = serde_json::from_str(&json).unwrap();
        assert_eq!(v["type"], "aborted");
        assert_eq!(v["data"], "wall_clock_timeout");
    }
}
