//! Phase 79 sub-phase 0: master/worker contract surface.

use std::sync::Arc;

use anyhow::Result;
use async_trait::async_trait;
use bytes::Bytes;
use folk_api::{
    Executor, NoopExecutor, Placement, Plugin, PluginContext, PluginRole, ServerPlugin,
    ServerPluginWrapper,
};
use tokio::sync::watch;

#[test]
fn role_predicates() {
    assert!(PluginRole::Master.is_master());
    assert!(!PluginRole::Master.is_worker());
    assert_eq!(PluginRole::Master.worker_id(), None);

    let w = PluginRole::Worker { worker_id: 3 };
    assert!(w.is_worker());
    assert!(!w.is_master());
    assert_eq!(w.worker_id(), Some(3));
}

#[test]
fn placement_includes() {
    let master = PluginRole::Master;
    let worker = PluginRole::Worker { worker_id: 0 };

    assert!(Placement::MasterOnly.includes(master));
    assert!(!Placement::MasterOnly.includes(worker));

    assert!(!Placement::WorkerOnly.includes(master));
    assert!(Placement::WorkerOnly.includes(worker));

    assert!(Placement::Both.includes(master));
    assert!(Placement::Both.includes(worker));
}

#[test]
fn plugin_context_builds_with_role() {
    let (_tx, rx) = watch::channel(false);
    let ctx = PluginContext {
        role: PluginRole::Worker { worker_id: 7 },
        executor: Arc::new(NoopExecutor),
        shutdown: rx,
        rpc_registrar: None,
        health_registry: None,
        metrics_registry: None,
        pool: None,
    };
    assert_eq!(ctx.role, PluginRole::Worker { worker_id: 7 });
}

struct DefaultPlacementPlugin;

#[async_trait]
impl Plugin for DefaultPlacementPlugin {
    fn name(&self) -> &'static str {
        "default-placement"
    }
    async fn boot(&mut self, _ctx: PluginContext) -> Result<()> {
        Ok(())
    }
    async fn shutdown(&self) -> Result<()> {
        Ok(())
    }
}

struct MasterServerPlugin;

#[async_trait]
impl ServerPlugin for MasterServerPlugin {
    fn name(&self) -> &'static str {
        "master-only"
    }
    async fn run(&self, _ctx: PluginContext) -> Result<()> {
        Ok(())
    }
    fn placement(&self) -> Placement {
        Placement::MasterOnly
    }
}

#[test]
fn placement_default_is_worker_only() {
    assert_eq!(DefaultPlacementPlugin.placement(), Placement::WorkerOnly);
}

#[test]
fn server_plugin_wrapper_forwards_placement() {
    // ServerPlugin override flows through the Plugin wrapper.
    let wrapper = ServerPluginWrapper::new(MasterServerPlugin);
    assert_eq!(wrapper.placement(), Placement::MasterOnly);
}

#[tokio::test]
async fn noop_executor_refuses() {
    // In release builds (no debug_assert) the call returns Err rather than panicking.
    let exec = NoopExecutor;
    let res = exec.execute_method("dispatch", Bytes::from("x")).await;
    assert!(res.is_err(), "NoopExecutor must refuse dispatch");
}
