use crate::{
    ActivityId, ActivityName, CommandId, CommandSeq, EventId, PayloadRef, RetryPolicy, RunId,
    SignalId, SignalName, TaskQueue, TimestampMs, WorkflowType,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CommandFingerprint {
    pub kind: CommandKind,
    pub name: String,
    pub input_digest: Option<String>,
    pub options_digest: String,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CommandKind {
    Activity,
    ActivityMap,
    Timer,
    Signal,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityScheduled {
    pub command_id: CommandId,
    pub activity_name: ActivityName,
    pub task_queue: TaskQueue,
    pub retry_policy: RetryPolicy,
    pub input: PayloadRef,
    pub fingerprint: CommandFingerprint,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityCompleted {
    pub command_id: CommandId,
    pub result: PayloadRef,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityFailed {
    pub command_id: CommandId,
    pub message: String,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimerStarted {
    pub command_id: CommandId,
    pub fire_at: TimestampMs,
    pub fingerprint: CommandFingerprint,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimerFired {
    pub command_id: CommandId,
    pub fired_at: TimestampMs,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SignalConsumed {
    pub command_id: CommandId,
    pub signal_id: SignalId,
    pub signal_name: SignalName,
    pub payload: PayloadRef,
    pub fingerprint: CommandFingerprint,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityMapInputManifest {
    pub items: Vec<PayloadRef>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityMapResultManifest {
    pub name: String,
    pub results: Vec<PayloadRef>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityMapScheduled {
    pub command_id: CommandId,
    pub activity_name: ActivityName,
    pub task_queue: TaskQueue,
    pub retry_policy: RetryPolicy,
    pub input_manifest: PayloadRef,
    pub result_manifest_name: String,
    pub max_in_flight: usize,
    pub fingerprint: CommandFingerprint,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityMapCompleted {
    pub command_id: CommandId,
    pub result_manifest: PayloadRef,
    pub item_count: usize,
    pub success_count: usize,
    pub failure_count: usize,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityMapFailed {
    pub command_id: CommandId,
    pub message: String,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum HistoryEventData {
    WorkflowStarted {
        workflow_type: WorkflowType,
        input: PayloadRef,
    },
    WorkflowCompleted {
        result: PayloadRef,
    },
    WorkflowFailed {
        message: String,
    },
    WorkflowTaskStarted,
    ActivityScheduled(ActivityScheduled),
    ActivityMapScheduled(ActivityMapScheduled),
    ActivityMapCompleted(ActivityMapCompleted),
    ActivityMapFailed(ActivityMapFailed),
    ActivityCompleted(ActivityCompleted),
    ActivityFailed(ActivityFailed),
    TimerStarted(TimerStarted),
    TimerFired(TimerFired),
    SignalConsumed(SignalConsumed),
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct HistoryEvent {
    pub event_id: EventId,
    pub event_type: HistoryEventType,
    pub data: HistoryEventData,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum HistoryEventType {
    WorkflowStarted,
    WorkflowCompleted,
    WorkflowFailed,
    WorkflowTaskStarted,
    ActivityScheduled,
    ActivityMapScheduled,
    ActivityMapCompleted,
    ActivityMapFailed,
    ActivityCompleted,
    ActivityFailed,
    TimerStarted,
    TimerFired,
    SignalConsumed,
}

impl HistoryEventData {
    pub fn event_type(&self) -> HistoryEventType {
        match self {
            Self::WorkflowStarted { .. } => HistoryEventType::WorkflowStarted,
            Self::WorkflowCompleted { .. } => HistoryEventType::WorkflowCompleted,
            Self::WorkflowFailed { .. } => HistoryEventType::WorkflowFailed,
            Self::WorkflowTaskStarted => HistoryEventType::WorkflowTaskStarted,
            Self::ActivityScheduled(_) => HistoryEventType::ActivityScheduled,
            Self::ActivityMapScheduled(_) => HistoryEventType::ActivityMapScheduled,
            Self::ActivityMapCompleted(_) => HistoryEventType::ActivityMapCompleted,
            Self::ActivityMapFailed(_) => HistoryEventType::ActivityMapFailed,
            Self::ActivityCompleted(_) => HistoryEventType::ActivityCompleted,
            Self::ActivityFailed(_) => HistoryEventType::ActivityFailed,
            Self::TimerStarted(_) => HistoryEventType::TimerStarted,
            Self::TimerFired(_) => HistoryEventType::TimerFired,
            Self::SignalConsumed(_) => HistoryEventType::SignalConsumed,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct NewHistoryEvent {
    pub data: HistoryEventData,
}

impl NewHistoryEvent {
    pub fn new(data: HistoryEventData) -> Self {
        Self { data }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityTask {
    pub activity_id: ActivityId,
    pub run_id: RunId,
    pub command_id: CommandId,
    pub activity_name: ActivityName,
    pub task_queue: TaskQueue,
    pub retry_policy: RetryPolicy,
    pub input: PayloadRef,
    pub map_item: Option<ActivityMapItem>,
}

impl ActivityTask {
    pub fn from_scheduled(scheduled: &ActivityScheduled) -> Self {
        Self {
            activity_id: ActivityId::new(&scheduled.command_id),
            run_id: scheduled.command_id.run_id.clone(),
            command_id: scheduled.command_id.clone(),
            activity_name: scheduled.activity_name.clone(),
            task_queue: scheduled.task_queue.clone(),
            retry_policy: scheduled.retry_policy.clone(),
            input: scheduled.input.clone(),
            map_item: None,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityMapItem {
    pub map_command_id: CommandId,
    pub item_ordinal: u64,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityMapTask {
    pub map_command_id: CommandId,
    pub activity_name: ActivityName,
    pub task_queue: TaskQueue,
    pub retry_policy: RetryPolicy,
    pub input_manifest: PayloadRef,
    pub result_manifest_name: String,
    pub max_in_flight: usize,
}

pub fn activity_fingerprint(
    activity_name: ActivityName,
    input_digest: String,
    options_digest: String,
) -> CommandFingerprint {
    CommandFingerprint {
        kind: CommandKind::Activity,
        name: activity_name.0,
        input_digest: Some(input_digest),
        options_digest,
    }
}

pub fn activity_map_fingerprint(
    activity_name: ActivityName,
    input_manifest_digest: String,
    result_manifest_name: String,
    max_in_flight: usize,
    options_digest: String,
) -> CommandFingerprint {
    CommandFingerprint {
        kind: CommandKind::ActivityMap,
        name: activity_name.0,
        input_digest: Some(input_manifest_digest),
        options_digest: format!(
            "{options_digest}:result={result_manifest_name}:max={max_in_flight}"
        ),
    }
}

pub fn timer_fingerprint(kind: &str, deadline: TimestampMs) -> CommandFingerprint {
    CommandFingerprint {
        kind: CommandKind::Timer,
        name: kind.to_owned(),
        input_digest: None,
        options_digest: format!("timestamp-ms:{}", deadline.0),
    }
}

pub fn signal_fingerprint(signal_name: SignalName) -> CommandFingerprint {
    CommandFingerprint {
        kind: CommandKind::Signal,
        name: signal_name.0,
        input_digest: None,
        options_digest: "sha256:default".to_owned(),
    }
}

pub fn command_id(run_id: &RunId, seq: u64) -> CommandId {
    CommandId {
        run_id: run_id.clone(),
        seq: CommandSeq(seq),
    }
}
