use crate::backend::AuthorizedToolBackend;
use crate::behavior::*;
use crate::config::BehaviorSettings;
use crate::perception::WorldPosition;
use crate::policy::{
    ActionOrigin, MemoryPolicyAudit, PolicyAuditSink, PolicyGateway, PolicyLimits,
    PolicyRequestContext,
};
use crate::types::{ProposedToolCall, ToolCallOutcome};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::CancellationToken;
use serde_json::{Value, json};
use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex};
use std::time::Duration;

fn uuid(number: u64) -> UUID {
    UUID::new_with_string(format!("00000000-0000-4000-8000-{number:012x}")).expect("UUID")
}

fn settings() -> BehaviorSettings {
    BehaviorSettings {
        heartbeat: Duration::from_secs(30),
        settle_delay: Duration::from_millis(10),
        response_delay_min: Duration::from_millis(20),
        response_delay_max: Duration::from_millis(20),
        attention_dwell: Duration::from_millis(30),
        idle_interval: Duration::from_secs(15),
        action_timeout: Duration::from_secs(5),
        max_walk_duration: Duration::from_secs(4),
        stuck_timeout: Duration::from_secs(1),
        min_action_interval: Duration::ZERO,
        max_walk_distance_meters: 8,
        max_attention_distance_meters: 96,
        idle_look_enabled: true,
    }
}

#[derive(Clone)]
struct FakeSink {
    state: Arc<Mutex<FakeState>>,
}

struct FakeState {
    pose: EmbodiedPose,
    avatars: BTreeMap<UUID, WorldPosition>,
    calls: Vec<String>,
    walk_target: Option<WorldPosition>,
    stuck: bool,
    walk_allowed: bool,
}

impl FakeSink {
    fn new(generation: u64, region_id: UUID) -> Self {
        Self {
            state: Arc::new(Mutex::new(FakeState {
                pose: EmbodiedPose {
                    generation,
                    region_id,
                    position: WorldPosition {
                        x: 128.0,
                        y: 128.0,
                        z: 24.0,
                    },
                    heading_degrees: 0.0,
                    sitting: false,
                },
                avatars: BTreeMap::from([(
                    uuid(2),
                    WorldPosition {
                        x: 132.0,
                        y: 128.0,
                        z: 24.0,
                    },
                )]),
                calls: Vec::new(),
                walk_target: None,
                stuck: false,
                walk_allowed: true,
            })),
        }
    }

    fn calls(&self) -> Vec<String> {
        self.state.lock().expect("state").calls.clone()
    }
    fn set_region(&self, region_id: UUID) {
        self.state.lock().expect("state").pose.region_id = region_id;
    }
    fn set_position(&self, position: WorldPosition) {
        self.state.lock().expect("state").pose.position = position;
    }
    fn set_stuck(&self, stuck: bool) {
        self.state.lock().expect("state").stuck = stuck;
    }
    fn set_walk_allowed(&self, allowed: bool) {
        self.state.lock().expect("state").walk_allowed = allowed;
    }
}

impl EmbodimentSink for FakeSink {
    fn current_pose(
        &self,
        generation: u64,
        _: CancellationToken,
    ) -> EmbodimentFuture<'_, EmbodiedPose> {
        Box::pin(async move {
            let mut state = self.state.lock().expect("state");
            if state.pose.generation != generation {
                return Err(BehaviorError::NotReady);
            }
            if let Some(target) = state.walk_target
                && !state.stuck
            {
                state.pose.position = target;
            }
            Ok(state.pose.clone())
        })
    }
    fn resolve_avatar(
        &self,
        generation: u64,
        id: UUID,
        _: CancellationToken,
    ) -> EmbodimentFuture<'_, WorldPosition> {
        Box::pin(async move {
            let state = self.state.lock().expect("state");
            if state.pose.generation != generation {
                return Err(BehaviorError::NotReady);
            }
            state
                .avatars
                .get(&id)
                .copied()
                .ok_or(BehaviorError::TargetUnavailable)
        })
    }
    fn face_point(
        &self,
        _: u64,
        _: WorldPosition,
        _: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            self.state.lock().expect("state").calls.push("face".into());
            Ok(())
        })
    }
    fn begin_walk(
        &self,
        _: u64,
        point: WorldPosition,
        _: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            let mut state = self.state.lock().expect("state");
            state.calls.push("walk".into());
            state.walk_target = Some(point);
            Ok(())
        })
    }
    fn validate_walk_target(
        &self,
        _: u64,
        _: WorldPosition,
        _: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            self.state
                .lock()
                .expect("state")
                .walk_allowed
                .then_some(())
                .ok_or(BehaviorError::RegionBoundary)
        })
    }
    fn stop(&self, _: u64, _: CancellationToken) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            let mut state = self.state.lock().expect("state");
            state.calls.push("stop".into());
            state.walk_target = None;
            Ok(())
        })
    }
    fn sit(&self, _: u64, _: CancellationToken) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            let mut state = self.state.lock().expect("state");
            state.calls.push("sit".into());
            state.pose.sitting = true;
            Ok(())
        })
    }
    fn stand(&self, _: u64, _: CancellationToken) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            let mut state = self.state.lock().expect("state");
            state.calls.push("stand".into());
            state.pose.sitting = false;
            Ok(())
        })
    }
    fn set_camera(
        &self,
        _: u64,
        _: WorldPosition,
        _: WorldPosition,
        _: f64,
        _: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            self.state
                .lock()
                .expect("state")
                .calls
                .push("camera_set".into());
            Ok(())
        })
    }
    fn reset_camera(&self, _: u64, _: CancellationToken) -> EmbodimentFuture<'_, ()> {
        Box::pin(async move {
            self.state
                .lock()
                .expect("state")
                .calls
                .push("camera_reset".into());
            Ok(())
        })
    }
}

struct FixedRandom(u64);
impl BehaviorRandom for FixedRandom {
    fn next_u64(&self) -> u64 {
        self.0
    }
}

fn authorize(
    settings: &BehaviorSettings,
    name: &str,
    arguments: &Value,
) -> crate::policy::AuthorizedAction {
    let avatar = uuid(900);
    let audit: Arc<dyn PolicyAuditSink> = Arc::new(MemoryPolicyAudit::new(64).expect("audit"));
    let gateway = PolicyGateway::new(
        BTreeSet::from([avatar]),
        behavior_policy_tools(settings).expect("tools"),
        PolicyLimits::default(),
        audit,
    )
    .expect("gateway");
    let context = PolicyRequestContext::new(
        ActionOrigin::instant_message(avatar),
        "behavior-session",
        "behavior-correlation",
    )
    .expect("context");
    let call = ProposedToolCall::new(model_call_id(), name, arguments.to_string()).expect("call");
    gateway
        .evaluate(&context, &call, arguments, None, 100)
        .expect("evaluation")
        .into_authorization()
        .expect("authorization")
}

fn model_call_id() -> String {
    format!("call_{}|fc_{}", "c".repeat(40), "d".repeat(60))
}

async fn run_tool(
    backend: &BehaviorBackend,
    settings: &BehaviorSettings,
    name: &str,
    args: Value,
) -> ToolCallOutcome {
    backend
        .apply(
            authorize(settings, name, &args),
            CancellationToken::default(),
        )
        .await
        .expect("backend")
}

#[test]
fn policy_exposes_only_bounded_high_level_actions() {
    let tools = behavior_policy_tools(&settings()).expect("tools");
    assert_eq!(tools.len(), 10);
    let names = tools
        .iter()
        .map(|tool| tool.definition.name.as_str())
        .collect::<Vec<_>>();
    assert_eq!(
        names,
        [
            FACE_AVATAR_TOOL,
            FACE_POINT_TOOL,
            LOOK_AROUND_TOOL,
            WALK_SHORT_TOOL,
            STOP_TOOL,
            SIT_TOOL,
            STAND_TOOL,
            CURRENT_POSE_TOOL,
            CAMERA_SET_TOOL,
            CAMERA_RESET_TOOL,
        ]
    );
    assert!(names.iter().all(|name| !name.contains("teleport")
        && !name.contains("control")
        && !name.contains("follow")));
}

#[tokio::test(start_paused = true)]
async fn camera_tools_validate_and_route_bounded_view_changes() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle =
        BehaviorController::new(config.clone(), sink.clone(), 16, 32, Duration::from_secs(1))
            .expect("controller")
            .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    let backend = BehaviorBackend::new(ingress);
    let set = run_tool(
        &backend,
        &config,
        CAMERA_SET_TOOL,
        json!({
            "position_x":126.0,"position_y":128.0,"position_z":26.0,
            "target_x":132.0,"target_y":128.0,"target_z":24.0,
            "vertical_fov_degrees":55.0
        }),
    )
    .await;
    assert!(matches!(set, ToolCallOutcome::Completed { .. }));
    let reset = run_tool(&backend, &config, CAMERA_RESET_TOOL, json!({})).await;
    assert!(matches!(reset, ToolCallOutcome::Completed { .. }));
    assert_eq!(sink.calls(), vec!["camera_set", "camera_reset"]);
    let invalid = run_tool(
        &backend,
        &config,
        CAMERA_SET_TOOL,
        json!({
            "position_x":126.0,"position_y":128.0,"position_z":26.0,
            "target_x":126.0,"target_y":128.0,"target_z":26.0,
            "vertical_fov_degrees":55.0
        }),
    )
    .await;
    assert!(matches!(invalid, ToolCallOutcome::Rejected { .. }));
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn public_attention_waits_turns_and_returns_to_available() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    let handle = BehaviorController::with_random(
        settings(),
        sink.clone(),
        16,
        32,
        Duration::from_secs(1),
        Arc::new(FixedRandom(0)),
    )
    .expect("controller")
    .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::time::advance(Duration::from_millis(11)).await;
    tokio::task::yield_now().await;
    let pacing = tokio::spawn(async move { ingress.attention("delivery-1".into(), uuid(2)).await });
    tokio::time::advance(Duration::from_millis(21)).await;
    tokio::task::yield_now().await;
    assert!(pacing.await.expect("task").is_ok());
    assert_eq!(sink.calls(), vec!["face"]);
    tokio::time::advance(Duration::from_millis(31)).await;
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn quarter_turn_uses_bounded_intermediate_camera_updates() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle =
        BehaviorController::new(config.clone(), sink.clone(), 16, 32, Duration::from_secs(1))
            .expect("controller")
            .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    let backend = BehaviorBackend::new(ingress);
    let turn = tokio::spawn({
        let config = config.clone();
        async move {
            run_tool(
                &backend,
                &config,
                FACE_POINT_TOOL,
                json!({"x":128.0,"y":132.0,"z":24.0}),
            )
            .await
        }
    });
    tokio::task::yield_now().await;
    tokio::time::advance(Duration::from_millis(100)).await;
    tokio::task::yield_now().await;
    assert!(matches!(
        turn.await.expect("turn"),
        ToolCallOutcome::Completed { .. }
    ));
    assert_eq!(sink.calls(), vec!["face", "face", "face"]);
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn bounded_walk_stops_and_region_change_rejects_stale_pose() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    sink.set_position(WorldPosition {
        x: 678.0,
        y: 538.0,
        z: 24.0,
    });
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle =
        BehaviorController::new(config.clone(), sink.clone(), 16, 32, Duration::from_secs(1))
            .expect("controller")
            .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    let backend = BehaviorBackend::new(ingress.clone());
    let task = tokio::spawn({
        let backend = backend.clone();
        let config = config.clone();
        async move {
            run_tool(
                &backend,
                &config,
                WALK_SHORT_TOOL,
                json!({"heading_degrees":0.0,"distance_meters":2.0}),
            )
            .await
        }
    });
    tokio::time::advance(Duration::from_millis(300)).await;
    tokio::task::yield_now().await;
    assert!(matches!(
        task.await.expect("task"),
        ToolCallOutcome::Completed { .. }
    ));
    assert_eq!(sink.calls(), vec!["face", "walk", "stop"]);
    let new_region = uuid(11);
    sink.set_region(new_region);
    ingress.region_changed(1, new_region).expect("cross");
    tokio::task::yield_now().await;
    let pose = run_tool(&backend, &config, CURRENT_POSE_TOOL, json!({})).await;
    assert!(matches!(pose, ToolCallOutcome::Completed { .. }));
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn parcel_boundary_rejects_walk_before_any_movement_update() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    sink.set_walk_allowed(false);
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle =
        BehaviorController::new(config.clone(), sink.clone(), 16, 32, Duration::from_secs(1))
            .expect("controller")
            .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    let backend = BehaviorBackend::new(ingress);
    assert!(matches!(
        run_tool(
            &backend,
            &config,
            WALK_SHORT_TOOL,
            json!({"heading_degrees":0.0,"distance_meters":2.0}),
        )
        .await,
        ToolCallOutcome::Rejected { .. }
    ));
    assert_eq!(sink.calls(), vec!["stop"]);
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn pause_preempts_stuck_walk_and_blocks_all_motion() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    sink.set_stuck(true);
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle =
        BehaviorController::new(config.clone(), sink.clone(), 16, 32, Duration::from_secs(1))
            .expect("controller")
            .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    let backend = BehaviorBackend::new(ingress.clone());
    let task = tokio::spawn({
        let backend = backend.clone();
        let config = config.clone();
        async move {
            run_tool(
                &backend,
                &config,
                WALK_SHORT_TOOL,
                json!({"heading_degrees":90.0,"distance_meters":2.0}),
            )
            .await
        }
    });
    tokio::time::advance(Duration::from_millis(300)).await;
    ingress.pause();
    tokio::task::yield_now().await;
    assert!(matches!(
        task.await.expect("task"),
        ToolCallOutcome::Rejected { .. }
    ));
    let before = sink.calls().len();
    assert!(matches!(
        run_tool(&backend, &config, SIT_TOOL, json!({})).await,
        ToolCallOutcome::Rejected { .. }
    ));
    assert_eq!(sink.calls().len(), before);
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn exact_action_id_cancels_only_the_matching_pending_action() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    sink.set_stuck(true);
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle = BehaviorController::new(config.clone(), sink, 16, 32, Duration::from_secs(1))
        .expect("controller")
        .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    let backend = BehaviorBackend::new(ingress.clone());
    let task = tokio::spawn({
        let backend = backend.clone();
        let config = config.clone();
        async move {
            run_tool(
                &backend,
                &config,
                WALK_SHORT_TOOL,
                json!({"heading_degrees":90.0,"distance_meters":2.0}),
            )
            .await
        }
    });
    tokio::time::advance(Duration::from_millis(300)).await;
    assert!(!ingress.cancel_action("unrelated-action").expect("cancel"));
    assert!(ingress.cancel_action(&model_call_id()).expect("cancel"));
    tokio::task::yield_now().await;
    assert!(matches!(
        task.await.expect("task"),
        ToolCallOutcome::Rejected { .. }
    ));
    assert!(matches!(
        run_tool(&backend, &config, SIT_TOOL, json!({})).await,
        ToolCallOutcome::Completed { .. }
    ));
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn offline_and_emergency_stop_emit_zero_motion() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle =
        BehaviorController::new(config.clone(), sink.clone(), 16, 32, Duration::from_secs(1))
            .expect("controller")
            .start();
    let ingress = handle.ingress();
    let backend = BehaviorBackend::new(ingress.clone());
    assert!(matches!(
        run_tool(&backend, &config, SIT_TOOL, json!({})).await,
        ToolCallOutcome::Rejected { .. }
    ));
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    ingress.emergency_stop();
    tokio::task::yield_now().await;
    let after_stop = sink.calls().len();
    assert!(matches!(
        run_tool(&backend, &config, STAND_TOOL, json!({})).await,
        ToolCallOutcome::Rejected { .. }
    ));
    assert_eq!(sink.calls().len(), after_stop);
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn seeded_idle_is_one_low_frequency_look_without_movement_or_chat() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    let handle = BehaviorController::with_random(
        config,
        sink.clone(),
        16,
        32,
        Duration::from_secs(1),
        Arc::new(FixedRandom(30)),
    )
    .expect("controller")
    .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    tokio::time::advance(Duration::from_secs(14)).await;
    tokio::task::yield_now().await;
    assert!(sink.calls().is_empty());
    tokio::time::advance(Duration::from_secs(2)).await;
    tokio::task::yield_now().await;
    assert_eq!(sink.calls(), vec!["face"]);
    handle.shutdown().await.expect("shutdown");
}

#[tokio::test(start_paused = true)]
async fn region_change_preempts_walk_and_disappearance_cancels_attention() {
    let region = uuid(10);
    let sink = Arc::new(FakeSink::new(1, region));
    sink.set_stuck(true);
    let mut config = settings();
    config.settle_delay = Duration::ZERO;
    config.response_delay_min = Duration::ZERO;
    config.response_delay_max = Duration::ZERO;
    let handle =
        BehaviorController::new(config.clone(), sink.clone(), 16, 32, Duration::from_secs(1))
            .expect("controller")
            .start();
    let ingress = handle.ingress();
    ingress.connected(1, region).expect("ready");
    tokio::task::yield_now().await;
    let backend = BehaviorBackend::new(ingress.clone());
    let task = tokio::spawn({
        let backend = backend.clone();
        let config = config.clone();
        async move {
            run_tool(
                &backend,
                &config,
                WALK_SHORT_TOOL,
                json!({"heading_degrees":180.0,"distance_meters":2.0}),
            )
            .await
        }
    });
    tokio::time::advance(Duration::from_millis(300)).await;
    let next = uuid(11);
    sink.set_region(next);
    ingress.region_changed(1, next).expect("cross");
    tokio::task::yield_now().await;
    assert!(matches!(
        task.await.expect("walk"),
        ToolCallOutcome::Rejected { .. }
    ));
    let missing = tokio::spawn({
        let ingress = ingress.clone();
        async move { ingress.attention("missing".into(), uuid(999)).await }
    });
    tokio::task::yield_now().await;
    assert_eq!(
        missing.await.expect("attention"),
        Err(BehaviorError::TargetUnavailable)
    );
    handle.shutdown().await.expect("shutdown");
}
