use crate::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;

/// Agent info included in delegation lifecycle events.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationAgentInfo {
    pub id: String,
    pub description: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AppEvent {
    ConfigUpdated,
    ChannelConnected {
        channel: String,
    },
    ChannelDisconnected {
        channel: String,
        reason: String,
    },
    ChannelReconnecting {
        channel: String,
        attempt: u32,
    },
    ChannelMessageReceived {
        channel: String,
        sender: String,
        session_id: String,
        content_preview: String,
        role: String,
    },
    HeartbeatTick {
        job_id: String,
    },
    CronFired {
        job_id: String,
        name: String,
    },
    HeartbeatAlert {
        message: String,
    },
    SchedulerStarted,
    SchedulerStopped,
    SchedulerNotification {
        job_id: String,
        job_name: String,
        message: String,
    },
    SchedulerJobCompleted {
        job_id: String,
        job_name: String,
        status: String,
        error: Option<String>,
    },
    DelegationStarted {
        delegation_id: String,
        agents: Vec<DelegationAgentInfo>,
    },
    SubAgentSpawned {
        delegation_id: String,
        agent_id: String,
        task: String,
    },
    SubAgentProgress {
        delegation_id: String,
        agent_id: String,
        tool_uses: u32,
        tokens_used: u64,
        current_activity: String,
    },
    SubAgentCompleted {
        delegation_id: String,
        agent_id: String,
        status: String,
        duration_ms: u64,
        tool_uses: u32,
        tokens_used: u64,
    },
    SubAgentFailed {
        delegation_id: String,
        agent_id: String,
        error: String,
        tool_uses: u32,
        duration_ms: u64,
    },
    DelegationCompleted {
        delegation_id: String,
        total_duration_ms: u64,
        total_tokens: u64,
    },
    WorkflowStarted {
        workflow_id: String,
        run_id: String,
    },
    WorkflowCompleted {
        workflow_id: String,
        run_id: String,
        status: String,
    },
    WorkflowStepCompleted {
        workflow_id: String,
        run_id: String,
        step_name: String,
        success: bool,
    },
    ChannelAgentStarted {
        channel: String,
        session_id: String,
        sender: String,
    },
    ChannelAgentCompleted {
        channel: String,
        session_id: String,
    },
    ApprovalRequested {
        approval_id: String,
        call_id: String,
        tool_name: String,
        args_summary: String,
        risk_level: String,
        reason: String,
        timeout_secs: u64,
    },
    SessionCreated {
        session_id: String,
        title: String,
        source: String,
    },
    SessionDeleted {
        session_id: String,
    },
    MessageAdded {
        session_id: String,
        message_id: String,
        role: String,
    },
    MemoryChanged,
    SchedulerJobsChanged,
    CredentialsChanged,
    ProvidersChanged,
    SkillsChanged,
    IdentityChanged,
    WorkflowsChanged,
    PluginsChanged,
    PermissionsChanged,
    Shutdown,
}

#[async_trait]
pub trait EventBus: Send + Sync {
    fn publish(&self, event: AppEvent) -> Result<()>;
    fn subscribe(&self) -> broadcast::Receiver<AppEvent>;
}

pub struct TokioBroadcastBus {
    sender: broadcast::Sender<AppEvent>,
}

impl TokioBroadcastBus {
    pub fn new(capacity: usize) -> Self {
        let (sender, _) = broadcast::channel(capacity);
        Self { sender }
    }
}

#[async_trait]
impl EventBus for TokioBroadcastBus {
    fn publish(&self, event: AppEvent) -> Result<()> {
        // Ignore error when there are no subscribers — this is expected during startup
        let _ = self.sender.send(event);
        Ok(())
    }

    fn subscribe(&self) -> broadcast::Receiver<AppEvent> {
        self.sender.subscribe()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn publish_and_subscribe() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::ConfigUpdated).unwrap();

        let event = rx.recv().await.unwrap();
        assert!(matches!(event, AppEvent::ConfigUpdated));
    }

    #[tokio::test]
    async fn multi_subscriber_fanout() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx1 = bus.subscribe();
        let mut rx2 = bus.subscribe();

        bus.publish(AppEvent::Shutdown).unwrap();

        let e1 = rx1.recv().await.unwrap();
        let e2 = rx2.recv().await.unwrap();
        assert!(matches!(e1, AppEvent::Shutdown));
        assert!(matches!(e2, AppEvent::Shutdown));
    }

    #[tokio::test]
    async fn channel_connected_event() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::ChannelConnected {
            channel: "telegram".into(),
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(matches!(event, AppEvent::ChannelConnected { channel } if channel == "telegram"));
    }

    // 16.40 — HeartbeatTick event serialization
    #[tokio::test]
    async fn heartbeat_tick_event() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::HeartbeatTick {
            job_id: "j1".into(),
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(matches!(event, AppEvent::HeartbeatTick { job_id } if job_id == "j1"));
    }

    // 16.41 — CronFired event serialization
    #[tokio::test]
    async fn cron_fired_event() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::CronFired {
            job_id: "j2".into(),
            name: "daily_check".into(),
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(
            matches!(event, AppEvent::CronFired { job_id, name } if job_id == "j2" && name == "daily_check")
        );
    }

    // 8.6.1.1 — SchedulerNotification event serde round-trip
    #[tokio::test]
    async fn scheduler_notification_event() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::SchedulerNotification {
            job_id: "j1".into(),
            job_name: "daily_check".into(),
            message: "All systems go".into(),
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(
            matches!(event, AppEvent::SchedulerNotification { job_id, job_name, message }
                if job_id == "j1" && job_name == "daily_check" && message == "All systems go")
        );
    }

    // 8.6.1.1b — SchedulerNotification JSON serde round-trip
    #[test]
    fn scheduler_notification_event_serde() {
        let event = AppEvent::SchedulerNotification {
            job_id: "j1".into(),
            job_name: "daily_check".into(),
            message: "All systems go".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::SchedulerNotification { job_id, job_name, message }
                if job_id == "j1" && job_name == "daily_check" && message == "All systems go")
        );
    }

    // 8.6.1.2 — SchedulerJobCompleted event serde round-trip
    #[tokio::test]
    async fn scheduler_job_completed_event() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::SchedulerJobCompleted {
            job_id: "j2".into(),
            job_name: "heartbeat".into(),
            status: "success".into(),
            error: None,
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(
            matches!(event, AppEvent::SchedulerJobCompleted { job_id, job_name, status, error }
                if job_id == "j2" && job_name == "heartbeat" && status == "success" && error.is_none())
        );
    }

    // 8.6.1.2b — SchedulerJobCompleted JSON serde round-trip
    #[test]
    fn scheduler_job_completed_event_serde() {
        let event = AppEvent::SchedulerJobCompleted {
            job_id: "j2".into(),
            job_name: "heartbeat".into(),
            status: "success".into(),
            error: Some("timeout".into()),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::SchedulerJobCompleted { job_id, job_name, status, error }
                if job_id == "j2" && job_name == "heartbeat" && status == "success" && error == Some("timeout".into()))
        );
    }

    // IN.11 — enriched ChannelMessageReceived event round-trip
    #[tokio::test]
    async fn channel_message_received_enriched() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::ChannelMessageReceived {
            channel: "telegram".into(),
            sender: "user123".into(),
            session_id: "sess-abc".into(),
            content_preview: "Hello there".into(),
            role: "user".into(),
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(
            matches!(event, AppEvent::ChannelMessageReceived { channel, sender, session_id, content_preview, role }
                if channel == "telegram" && sender == "user123" && session_id == "sess-abc"
                && content_preview == "Hello there" && role == "user")
        );
    }

    // IN.12 — enriched ChannelMessageReceived JSON serde round-trip
    #[test]
    fn channel_message_received_serde() {
        let event = AppEvent::ChannelMessageReceived {
            channel: "slack".into(),
            sender: "bot".into(),
            session_id: "s1".into(),
            content_preview: "Hi".into(),
            role: "assistant".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::ChannelMessageReceived { channel, role, .. }
                if channel == "slack" && role == "assistant")
        );
    }

    // SUP.10 — ChannelReconnecting event round-trip
    #[tokio::test]
    async fn channel_reconnecting_event() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::ChannelReconnecting {
            channel: "telegram".into(),
            attempt: 3,
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(
            matches!(event, AppEvent::ChannelReconnecting { channel, attempt } if channel == "telegram" && attempt == 3)
        );
    }

    // SUP.11 — ChannelReconnecting JSON serde round-trip
    #[test]
    fn channel_reconnecting_event_serde() {
        let event = AppEvent::ChannelReconnecting {
            channel: "slack".into(),
            attempt: 5,
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::ChannelReconnecting { channel, attempt } if channel == "slack" && attempt == 5)
        );
    }

    // 7.25 — SubAgentSpawned event serde round-trip
    #[test]
    fn sub_agent_spawned_event_serde() {
        let event = AppEvent::SubAgentSpawned {
            delegation_id: "d1".into(),
            agent_id: "t1".into(),
            task: "research topic".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::SubAgentSpawned { delegation_id, agent_id, task }
                if delegation_id == "d1" && agent_id == "t1" && task == "research topic")
        );
    }

    // 7.26 — SubAgentCompleted event serde round-trip
    #[test]
    fn sub_agent_completed_event_serde() {
        let event = AppEvent::SubAgentCompleted {
            delegation_id: "d1".into(),
            agent_id: "t1".into(),
            status: "completed".into(),
            duration_ms: 1500,
            tool_uses: 5,
            tokens_used: 12300,
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::SubAgentCompleted { delegation_id, agent_id, status, duration_ms, tool_uses, tokens_used }
                if delegation_id == "d1" && agent_id == "t1" && status == "completed"
                && duration_ms == 1500 && tool_uses == 5 && tokens_used == 12300)
        );
    }

    // 7.27 — SubAgentFailed event serde round-trip
    #[test]
    fn sub_agent_failed_event_serde() {
        let event = AppEvent::SubAgentFailed {
            delegation_id: "d1".into(),
            agent_id: "t2".into(),
            error: "task timed out".into(),
            tool_uses: 3,
            duration_ms: 1200,
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::SubAgentFailed { delegation_id, agent_id, error, tool_uses, duration_ms }
                if delegation_id == "d1" && agent_id == "t2" && error == "task timed out" && tool_uses == 3 && duration_ms == 1200)
        );
    }

    // D.1 — DelegationStarted event serde round-trip
    #[test]
    fn delegation_started_event_serde() {
        let event = AppEvent::DelegationStarted {
            delegation_id: "d1".into(),
            agents: vec![
                DelegationAgentInfo {
                    id: "t1".into(),
                    description: "Research web".into(),
                },
                DelegationAgentInfo {
                    id: "t2".into(),
                    description: "Analyze code".into(),
                },
            ],
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::DelegationStarted { delegation_id, agents }
            if delegation_id == "d1" && agents.len() == 2)
        );
    }

    // D.2 — SubAgentProgress event serde round-trip
    #[test]
    fn sub_agent_progress_event_serde() {
        let event = AppEvent::SubAgentProgress {
            delegation_id: "d1".into(),
            agent_id: "t1".into(),
            tool_uses: 5,
            tokens_used: 12300,
            current_activity: "WebSearch: rust frameworks".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::SubAgentProgress { delegation_id, agent_id, tool_uses, tokens_used, current_activity }
            if delegation_id == "d1" && agent_id == "t1" && tool_uses == 5
            && tokens_used == 12300 && current_activity == "WebSearch: rust frameworks")
        );
    }

    // D.3 — DelegationCompleted event serde round-trip
    #[test]
    fn delegation_completed_event_serde() {
        let event = AppEvent::DelegationCompleted {
            delegation_id: "d1".into(),
            total_duration_ms: 5000,
            total_tokens: 45000,
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::DelegationCompleted { delegation_id, total_duration_ms, total_tokens }
            if delegation_id == "d1" && total_duration_ms == 5000 && total_tokens == 45000)
        );
    }

    // D.4 — DelegationAgentInfo serde round-trip
    #[test]
    fn delegation_agent_info_serde() {
        let info = DelegationAgentInfo {
            id: "t1".into(),
            description: "Research web frameworks".into(),
        };
        let json = serde_json::to_string(&info).unwrap();
        let back: DelegationAgentInfo = serde_json::from_str(&json).unwrap();
        assert_eq!(back.id, "t1");
        assert_eq!(back.description, "Research web frameworks");
    }

    #[tokio::test]
    async fn session_created_event_broadcast() {
        let bus = TokioBroadcastBus::new(16);
        let mut rx = bus.subscribe();

        bus.publish(AppEvent::SessionCreated {
            session_id: "sess-1".into(),
            title: "Test Chat".into(),
            source: "api".into(),
        })
        .unwrap();

        let event = rx.recv().await.unwrap();
        assert!(
            matches!(event, AppEvent::SessionCreated { session_id, title, source }
                if session_id == "sess-1" && title == "Test Chat" && source == "api")
        );
    }

    #[test]
    fn session_created_event_serde() {
        let event = AppEvent::SessionCreated {
            session_id: "sess-1".into(),
            title: "Test Chat".into(),
            source: "api".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::SessionCreated { session_id, title, source }
                if session_id == "sess-1" && title == "Test Chat" && source == "api")
        );
    }

    #[test]
    fn session_deleted_event_serde() {
        let event = AppEvent::SessionDeleted {
            session_id: "sess-2".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(matches!(back, AppEvent::SessionDeleted { session_id } if session_id == "sess-2"));
    }

    #[test]
    fn message_added_event_serde() {
        let event = AppEvent::MessageAdded {
            session_id: "sess-1".into(),
            message_id: "msg-1".into(),
            role: "user".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::MessageAdded { session_id, message_id, role }
                if session_id == "sess-1" && message_id == "msg-1" && role == "user")
        );
    }

    #[tokio::test]
    async fn publish_without_subscribers_is_ok() {
        let bus = TokioBroadcastBus::new(16);
        let result = bus.publish(AppEvent::ConfigUpdated);
        assert!(result.is_ok());
    }

    // 5.52 — WorkflowStarted event serde round-trip
    #[test]
    fn workflow_started_event_serde() {
        let event = AppEvent::WorkflowStarted {
            workflow_id: "wf1".into(),
            run_id: "run1".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::WorkflowStarted { workflow_id, run_id }
                if workflow_id == "wf1" && run_id == "run1")
        );
    }

    // 5.53 — WorkflowCompleted event serde round-trip
    #[test]
    fn workflow_completed_event_serde() {
        let event = AppEvent::WorkflowCompleted {
            workflow_id: "wf1".into(),
            run_id: "run1".into(),
            status: "success".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::WorkflowCompleted { workflow_id, run_id, status }
                if workflow_id == "wf1" && run_id == "run1" && status == "success")
        );
    }

    // TA.1 — ChannelAgentStarted event serde round-trip
    #[test]
    fn channel_agent_started_event_serde() {
        let event = AppEvent::ChannelAgentStarted {
            channel: "telegram".into(),
            session_id: "sess-1".into(),
            sender: "user42".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::ChannelAgentStarted { channel, session_id, sender }
                if channel == "telegram" && session_id == "sess-1" && sender == "user42")
        );
    }

    // TA.2 — ChannelAgentCompleted event serde round-trip
    #[test]
    fn channel_agent_completed_event_serde() {
        let event = AppEvent::ChannelAgentCompleted {
            channel: "slack".into(),
            session_id: "sess-2".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::ChannelAgentCompleted { channel, session_id }
                if channel == "slack" && session_id == "sess-2")
        );
    }

    // TA.3 — ApprovalRequested event serde round-trip
    #[test]
    fn approval_requested_event_serde() {
        let event = AppEvent::ApprovalRequested {
            approval_id: "apr-1".into(),
            call_id: "call-1".into(),
            tool_name: "shell".into(),
            args_summary: "cargo build".into(),
            risk_level: "medium".into(),
            reason: "Command needs approval: cargo build".into(),
            timeout_secs: 120,
        };
        let json = serde_json::to_string(&event).unwrap();
        let back: AppEvent = serde_json::from_str(&json).unwrap();
        assert!(
            matches!(back, AppEvent::ApprovalRequested { approval_id, call_id, tool_name, args_summary, risk_level, reason, timeout_secs }
                if approval_id == "apr-1" && call_id == "call-1" && tool_name == "shell"
                && args_summary == "cargo build" && risk_level == "medium"
                && reason == "Command needs approval: cargo build" && timeout_secs == 120)
        );
    }
}
