//! Illustrative `OracleHandler` impl — a "manual approval" oracle that
//! responds to every observation request whose subject contains the string
//! `"approve"` with `ProofClaim::ManualApproval`.
//!
//! Not a deployable binary — it's a reference template. Copy the
//! `MyOracle` struct + its `OracleHandler` impl into your own crate, swap
//! the decision logic for whatever your oracle observes (GitHub, an HTTP
//! source, a database, etc.), and you have a working oracle.
//!
//! ## Run
//!
//! ```text
//! cargo run --example manual_oracle -- <node-url> <oracle-key-file>
//! ```
//!
//! The key file must already exist (generate one with the chain repo's
//! `fialucci-oracle-keygen` binary, or via [`OracleIdentity::generate`] +
//! [`OracleIdentity::save_to_file`] in a separate script).
//!
//! The oracle must already be registered on the target node (the chain's
//! `oracles.json` registry, or auto-register if the node has that enabled).

use std::time::Duration;

use async_trait::async_trait;
use fialucci_oracle::types::{hash_evidence_bytes, ProofClaim, ProofEvidence, ProofSubject};
use fialucci_oracle::{
    HandlerError, ObservationRequest, Oracle, OracleHandler, OracleIdentity, ProofClaimEvidence,
};

struct ManualApprovalOracle;

#[async_trait]
impl OracleHandler for ManualApprovalOracle {
    async fn observe(
        &self,
        request: &ObservationRequest,
    ) -> Result<Option<ProofClaimEvidence>, HandlerError> {
        // The dev's entire job: decide what to claim, attach evidence.
        // Identity, chain_id, nonce, timestamp, signing, and submission are
        // not the dev's concern.
        let subject_hint = match &request.subject {
            ProofSubject::ExternalId(s) => s.clone(),
            other => format!("{other:?}"),
        };

        if !subject_hint.contains("approve") {
            return Ok(None);
        }

        // For a real oracle this is where you'd query whatever off-chain
        // source backs the assertion (a GitHub PR's merge state, a price
        // feed, a database row, etc.) and produce the bytes you want
        // committed on-chain as the evidence hash.
        let evidence_bytes = format!(r#"{{"manually_approved":"{subject_hint}"}}"#);
        let evidence = ProofEvidence {
            evidence_hash: hash_evidence_bytes(evidence_bytes.as_bytes()),
            evidence_uri: None,
            content_type: Some("application/json".to_string()),
            metadata: None,
        };

        Ok(Some(ProofClaimEvidence {
            claim: ProofClaim::ManualApproval,
            evidence,
        }))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = std::env::args().skip(1);
    let node_url = args
        .next()
        .ok_or("usage: cargo run --example manual_oracle -- <node-url> <oracle-key-file>")?;
    let key_file = args
        .next()
        .ok_or("usage: cargo run --example manual_oracle -- <node-url> <oracle-key-file>")?;

    let identity = OracleIdentity::load_from_file(&key_file)?;
    eprintln!("oracle id: {:?}", identity);

    let oracle = Oracle::builder()
        .identity(identity)
        .node_url(node_url)
        .handler(ManualApprovalOracle)
        .poll_interval(Duration::from_secs(3))
        .build()
        .await?;

    eprintln!(
        "connected to chain `{}` as oracle {}",
        oracle.chain_id(),
        hex::encode(oracle.oracle_id().0)
    );
    oracle.run().await?;
    Ok(())
}
