use std::any::Any;
use std::collections::BTreeMap;
use std::fmt;
use std::path::Path;
use std::sync::Arc;

use runx_contracts::{AuthorityVerb, JsonObject, Receipt, Reference};
use runx_core::state_machine::AuthorityAdmissionWitness;
use runx_parser::GraphStep;

use crate::adapter::SkillOutput;

use super::RuntimeEffectError;

pub trait RuntimeEffect: Send + Sync {
    fn family(&self) -> &'static str;

    fn can_run_parallel(&self, step: &GraphStep) -> bool {
        let _ = step;
        true
    }

    fn find_replay(
        &self,
        request: EffectStepRequest<'_>,
    ) -> Result<Option<EffectReplay>, RuntimeEffectError> {
        let _ = request;
        Ok(None)
    }

    fn recover_pending(&self, request: EffectStepRequest<'_>) -> Result<(), RuntimeEffectError> {
        let _ = request;
        Ok(())
    }

    fn admit(
        &self,
        request: EffectStepRequest<'_>,
    ) -> Result<Option<EffectAdmission>, RuntimeEffectError> {
        let _ = request;
        Ok(None)
    }

    fn prepare_output(&self, request: EffectOutputRequest<'_>) -> Result<(), RuntimeEffectError> {
        let _ = request;
        Ok(())
    }

    fn finalize_output(&self, request: EffectReceiptRequest<'_>) -> Result<(), RuntimeEffectError> {
        let _ = request;
        Ok(())
    }

    fn persist(&self, request: EffectReceiptRequest<'_>) -> Result<(), RuntimeEffectError> {
        let _ = request;
        Ok(())
    }

    fn prepare_replay_output(
        &self,
        request: EffectReplayOutputRequest<'_>,
    ) -> Result<(), RuntimeEffectError> {
        let _ = request;
        Ok(())
    }

    fn validate_replay(
        &self,
        request: EffectReplayReceiptRequest<'_>,
    ) -> Result<(), RuntimeEffectError> {
        let _ = request;
        Ok(())
    }

    fn refresh_output_metadata(
        &self,
        request: EffectMetadataRefreshRequest<'_>,
    ) -> Result<(), RuntimeEffectError> {
        let _ = request;
        Ok(())
    }

    fn authority_grant_refs(
        &self,
        admission: &EffectAdmission,
    ) -> Result<Vec<Reference>, RuntimeEffectError> {
        let _ = admission;
        Ok(Vec::new())
    }

    fn replay_authority_grant_refs(
        &self,
        replay: &EffectReplay,
    ) -> Result<Vec<Reference>, RuntimeEffectError> {
        let _ = replay;
        Ok(Vec::new())
    }
}

#[derive(Clone, Copy, Debug)]
pub struct EffectStepRequest<'a> {
    pub step: &'a GraphStep,
    pub inputs: &'a JsonObject,
    pub env: &'a BTreeMap<String, String>,
    pub graph_dir: &'a Path,
}

pub struct EffectOutputRequest<'a> {
    pub step: &'a GraphStep,
    pub admission: &'a EffectAdmission,
    pub claim: &'a JsonObject,
    pub output: &'a mut SkillOutput,
}

pub struct EffectReceiptRequest<'a> {
    pub step: &'a GraphStep,
    pub graph_dir: &'a Path,
    pub admission: &'a EffectAdmission,
    pub claim: &'a JsonObject,
    pub output: &'a mut SkillOutput,
    pub receipt: &'a Receipt,
    pub env: &'a BTreeMap<String, String>,
}

pub struct EffectReplayOutputRequest<'a> {
    pub step: &'a GraphStep,
    pub replay: &'a EffectReplay,
    pub output: &'a mut SkillOutput,
}

pub struct EffectReplayReceiptRequest<'a> {
    pub step: &'a GraphStep,
    pub replay: &'a EffectReplay,
    pub receipt: &'a Receipt,
    pub output: &'a SkillOutput,
    pub claim: &'a JsonObject,
}

pub struct EffectMetadataRefreshRequest<'a> {
    pub output: &'a mut SkillOutput,
    pub receipt: &'a Receipt,
}

#[derive(Clone)]
pub struct EffectAdmission {
    family: &'static str,
    verb: AuthorityVerb,
    witness: AuthorityAdmissionWitness,
    context: Arc<dyn Any + Send + Sync>,
}

impl EffectAdmission {
    #[must_use]
    pub fn new<T>(
        family: &'static str,
        verb: AuthorityVerb,
        witness: AuthorityAdmissionWitness,
        context: T,
    ) -> Self
    where
        T: Any + Send + Sync + 'static,
    {
        Self {
            family,
            verb,
            witness,
            context: Arc::new(context),
        }
    }

    #[must_use]
    pub fn family(&self) -> &'static str {
        self.family
    }

    #[must_use]
    pub fn verb(&self) -> AuthorityVerb {
        self.verb.clone()
    }

    #[must_use]
    pub fn witness(&self) -> &AuthorityAdmissionWitness {
        &self.witness
    }

    #[must_use]
    pub fn context<T: Any>(&self) -> Option<&T> {
        self.context.as_ref().downcast_ref::<T>()
    }
}

impl fmt::Debug for EffectAdmission {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("EffectAdmission")
            .field("family", &self.family)
            .field("verb", &self.verb)
            .field("witness", &self.witness)
            .finish_non_exhaustive()
    }
}

#[derive(Clone)]
pub struct EffectReplay {
    family: &'static str,
    receipt_ref: String,
    receipt_created_at: String,
    receipt_digest: String,
    outputs: JsonObject,
    context: Arc<dyn Any + Send + Sync>,
}

impl EffectReplay {
    #[must_use]
    pub fn new<T>(
        family: &'static str,
        receipt_ref: impl Into<String>,
        receipt_created_at: impl Into<String>,
        receipt_digest: impl Into<String>,
        outputs: JsonObject,
        context: T,
    ) -> Self
    where
        T: Any + Send + Sync + 'static,
    {
        Self {
            family,
            receipt_ref: receipt_ref.into(),
            receipt_created_at: receipt_created_at.into(),
            receipt_digest: receipt_digest.into(),
            outputs,
            context: Arc::new(context),
        }
    }

    #[must_use]
    pub fn family(&self) -> &'static str {
        self.family
    }

    #[must_use]
    pub fn receipt_ref(&self) -> &str {
        &self.receipt_ref
    }

    #[must_use]
    pub fn receipt_created_at(&self) -> &str {
        &self.receipt_created_at
    }

    #[must_use]
    pub fn receipt_digest(&self) -> &str {
        &self.receipt_digest
    }

    #[must_use]
    pub fn outputs(&self) -> &JsonObject {
        &self.outputs
    }

    #[must_use]
    pub fn context<T: Any>(&self) -> Option<&T> {
        self.context.as_ref().downcast_ref::<T>()
    }
}

impl fmt::Debug for EffectReplay {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("EffectReplay")
            .field("family", &self.family)
            .field("receipt_ref", &self.receipt_ref)
            .field("receipt_created_at", &self.receipt_created_at)
            .field("receipt_digest", &self.receipt_digest)
            .finish_non_exhaustive()
    }
}
