//! S25 Phase 5a — verify the handler-method shape end-to-end:
//!
//! 1. `#[handler]` on a `&self` method emits a thunk that downcasts
//!    `&dyn ModelPlugin` to the concrete plugin and invokes the method.
//! 2. `#[plugin]` builds a dispatch table sorted by `method_name`, so
//!    each `LocalHandlerId` routes to a specific method.
//! 3. Plugin state (held in `&self`) is reachable from the handler.

use std::sync::atomic::{AtomicU32, Ordering};

use maat_sdk::events::{EventKind, GameEvent};
use maat_sdk::handler::{Decision, HandlerResponse};
use maat_engine::plugin::{LocalHandlerId, ModelPlugin};
use maat_sdk::plugin::slices::GameContext;
use maat_sdk::plugin::PluginBackend;
use maat_sdk::priority::Priority;
use maat_sdk::traits::PipelineRole;
use maat_sdk::{EntityId, SeatId};
use maat_sdk_derive::{handlers, model_plugin, plugin};
use maat_sdk::board::FaceId;

#[plugin(
    canonical_id = "github.com/maat/test-stub-dispatch",
    display_name = "Stub Dispatch",
    license = "MIT"
)]
#[derive(Default)]
pub struct StubPlugin {
    counter: AtomicU32,
}

#[handlers]
impl StubPlugin {
    // Method names sort `handle_bar` < `handle_foo`, so `local_id 0`
    // routes to `handle_bar` and `local_id 1` routes to `handle_foo`.
    #[handler(
        event = EventKind::Moved,
        role = PipelineRole::Filter,
        priority = Priority::BASE,
        name = "stub:foo"
    )]
    fn handle_foo(&self, _event: &GameEvent, _ctx: &impl GameContext) -> HandlerResponse {
        self.counter.fetch_add(1, Ordering::SeqCst);
        HandlerResponse::from(Decision::Allow)
    }

    #[handler(
        event = EventKind::PoolChanged,
        role = PipelineRole::Filter,
        priority = Priority::BASE,
        name = "stub:bar"
    )]
    fn handle_bar(&self, _event: &GameEvent, _ctx: &impl GameContext) -> HandlerResponse {
        self.counter.fetch_add(10, Ordering::SeqCst);
        HandlerResponse::from(Decision::Allow)
    }
}

#[model_plugin]
impl ModelPlugin for StubPlugin {
    fn manifest(&self) -> &maat_sdk::plugin::PluginManifest {
        Self::manifest()
    }
    fn run_setup(&mut self, _ctx: &mut dyn maat_sdk::SetupContext) {}
    fn backend(&self) -> PluginBackend {
        PluginBackend::Static
    }
}

// Minimal stub context — none of the methods are exercised from the
// dispatched handlers in this test.
struct StubCtx {
    store: maat_sdk::entity_state::EntityState,
    settings: maat_sdk::SettingsBundle,
}

impl StubCtx {
    fn new() -> Self {
        Self {
            store: maat_sdk::entity_state::EntityState::new(),
            settings: maat_sdk::SettingsBundle::default(),
        }
    }
}

impl GameContext for StubCtx {
    fn store(&self) -> &maat_sdk::entity_state::EntityState { &self.store }
    fn current_player(&self) -> Option<SeatId> { None }
    fn settings(&self) -> &maat_sdk::SettingsBundle { &self.settings }
    fn alloc_id(&self) -> EntityId { self.store.alloc_id() }
    fn is_owners_turn(&self, _: EntityId) -> bool { false }
    fn contained_by(&self, _: EntityId) -> Option<EntityId> { None }
    fn entity_view_for(&self, _: EntityId) -> Option<maat_sdk::entity_state::EntityView> { None }
    fn this_turn_events(
        &self,
    ) -> Box<dyn Iterator<Item = &maat_sdk::events::LogEntry> + '_> {
        Box::new(::std::iter::empty())
    }
    fn this_phase_events(
        &self,
    ) -> Box<dyn Iterator<Item = &maat_sdk::events::LogEntry> + '_> {
        Box::new(::std::iter::empty())
    }
    fn since(
        &self,
        _: u64,
    ) -> Box<dyn Iterator<Item = &maat_sdk::events::LogEntry> + '_> {
        Box::new(::std::iter::empty())
    }
}

fn moved_event() -> GameEvent {
    GameEvent::Moved {
        entity: EntityId::from_raw(0),
        from: maat_sdk::Position::Face(FaceId(0)),
        to: maat_sdk::Position::Face(FaceId(1)),
        qualifiers: Vec::new(),
    }
}

#[test]
fn dispatch_handler_routes_each_local_id_to_correct_method() {
    let plugin = StubPlugin::default();
    let ctx = StubCtx::new();
    let evt = moved_event();

    // local_id 0 → handle_bar (alphabetically first), counter += 10.
    plugin.dispatch_handler(LocalHandlerId(0), &evt, &ctx);
    assert_eq!(plugin.counter.load(Ordering::SeqCst), 10);

    // local_id 1 → handle_foo, counter += 1.
    plugin.dispatch_handler(LocalHandlerId(1), &evt, &ctx);
    assert_eq!(plugin.counter.load(Ordering::SeqCst), 11);
}

#[test]
fn manifest_local_id_aligns_with_dispatch_table() {
    let m = StubPlugin::manifest();
    // declared_handlers position is the local_id; the wire name
    // tells us which underlying method it points at.
    let names: Vec<&str> = m.declared_handlers.iter().map(|h| h.name.as_str()).collect();
    // Method-name sort: handle_bar < handle_foo.
    assert_eq!(names, vec!["stub:bar", "stub:foo"]);
}

#[test]
fn handler_can_read_self_state() {
    // Side-effects above already prove this, but assert explicitly:
    // the &self handler observed and mutated `self.counter`.
    let plugin = StubPlugin::default();
    let ctx = StubCtx::new();
    let evt = moved_event();
    assert_eq!(plugin.counter.load(Ordering::SeqCst), 0);
    plugin.dispatch_handler(LocalHandlerId(1), &evt, &ctx); // handle_foo
    assert_eq!(plugin.counter.load(Ordering::SeqCst), 1);
}

#[test]
fn unknown_local_id_returns_allow_default() {
    let plugin = StubPlugin::default();
    let ctx = StubCtx::new();
    let evt = moved_event();
    // The plugin has 2 handlers; local_id 999 is out of range.
    let resp = plugin.dispatch_handler(LocalHandlerId(999), &evt, &ctx);
    assert!(matches!(resp.decision, Decision::Allow));
    assert_eq!(plugin.counter.load(Ordering::SeqCst), 0);
}
