use crate::behavior::{BehaviorController, BehaviorError, EmbodimentFuture, EmbodimentSink};
use crate::control_plane::{
    CONTROL_PROTOCOL_VERSION, ControlLimits, ControlPayload, ControlPlane, ControlRequest,
    ControlRequestEnvelope, PageRequest,
};
use crate::control_runtime::{AgentControlTarget, RuntimeControlCommand};
use crate::conversation::{ConversationLimits, ConversationStore};
use crate::perception::WorldPosition;
use crate::policy::{MemoryPolicyAudit, PolicyAuditSink, PolicyGateway, PolicyLimits};
use crate::{
    AgentConfig, EmbodiedPose, Observability, ObservabilityLimits, SecretString, VisionControl,
};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::CancellationToken;
use std::collections::BTreeSet;
use std::sync::{Arc, Mutex};
use std::time::Duration;

struct Sink;

struct FakeVision(Mutex<Option<String>>);
impl crate::VisionControl for FakeVision {
    fn cancel_capture(&self, correlation_id: &str) -> bool {
        let mut active = self.0.lock().unwrap();
        if active.as_deref() == Some(correlation_id) {
            *active = None;
            true
        } else {
            false
        }
    }
    fn active_capture(&self) -> Option<String> {
        self.0.lock().unwrap().clone()
    }
    fn capture_progress(&self) -> Option<String> {
        self.active_capture().map(|_| "rendering".into())
    }
    fn last_image_sha256(&self) -> Option<String> {
        Some("abc123".into())
    }
}

impl EmbodimentSink for Sink {
    fn current_pose(
        &self,
        generation: u64,
        _cancellation: CancellationToken,
    ) -> EmbodimentFuture<'_, EmbodiedPose> {
        Box::pin(async move {
            Ok(EmbodiedPose {
                generation,
                region_id: UUID::zero(),
                position: WorldPosition {
                    x: 128.0,
                    y: 128.0,
                    z: 24.0,
                },
                heading_degrees: 0.0,
                sitting: false,
            })
        })
    }

    fn resolve_avatar(
        &self,
        _generation: u64,
        _avatar_id: UUID,
        _cancellation: CancellationToken,
    ) -> EmbodimentFuture<'_, WorldPosition> {
        Box::pin(async { Err(BehaviorError::TargetUnavailable) })
    }

    fn face_point(
        &self,
        _generation: u64,
        _point: WorldPosition,
        _cancellation: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async { Ok(()) })
    }

    fn begin_walk(
        &self,
        _generation: u64,
        _point: WorldPosition,
        _cancellation: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async { Ok(()) })
    }

    fn validate_walk_target(
        &self,
        _generation: u64,
        _point: WorldPosition,
        _cancellation: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async { Ok(()) })
    }

    fn stop(&self, _generation: u64, _cancellation: CancellationToken) -> EmbodimentFuture<'_, ()> {
        Box::pin(async { Ok(()) })
    }

    fn sit(&self, _generation: u64, _cancellation: CancellationToken) -> EmbodimentFuture<'_, ()> {
        Box::pin(async { Ok(()) })
    }

    fn stand(
        &self,
        _generation: u64,
        _cancellation: CancellationToken,
    ) -> EmbodimentFuture<'_, ()> {
        Box::pin(async { Ok(()) })
    }
}

fn envelope(id: &str, request: ControlRequest) -> ControlRequestEnvelope {
    ControlRequestEnvelope {
        version: CONTROL_PROTOCOL_VERSION,
        request_id: id.to_owned(),
        request,
    }
}

#[tokio::test]
#[allow(clippy::too_many_lines)]
async fn production_target_projects_state_and_routes_real_mutations() {
    let settings = AgentConfig::offline("https://llm.invalid/chat", "llm-key")
        .expect("offline config")
        .behavior;
    let behavior = BehaviorController::new(settings, Arc::new(Sink), 8, 8, Duration::from_secs(1))
        .expect("behavior")
        .start();
    let audit = Arc::new(MemoryPolicyAudit::new(32).expect("audit"));
    let audit_sink: Arc<dyn PolicyAuditSink> = audit.clone();
    let policy = Arc::new(
        PolicyGateway::new(
            BTreeSet::new(),
            Vec::new(),
            PolicyLimits::default(),
            audit_sink,
        )
        .expect("policy"),
    );
    let conversations = Arc::new(
        ConversationStore::open(ConversationLimits::default(), None).expect("conversations"),
    );
    let (target, mut commands) =
        AgentControlTarget::new(conversations, policy, audit, behavior.ingress(), 8)
            .expect("target");
    let observability = Observability::memory(ObservabilityLimits::default()).expect("events");
    target.attach_observability(observability);
    let vision = Arc::new(FakeVision(Mutex::new(Some("visual-1".into()))));
    target.attach_vision_control(vision.clone());
    target.update_region(
        Some("00000000-0000-4000-8000-000000000001".into()),
        Some(9_007_199_254_740_992),
        Some("Test Region".into()),
        Some([128.0, 128.0, 24.0]),
    );
    let plane = ControlPlane::new(
        target.clone(),
        SecretString::new("test.operator", "operator-token").expect("token"),
        Some(SecretString::new("test.observer", "observer-token").expect("token")),
        ControlLimits::default(),
    )
    .expect("plane");
    let operator = plane.connect("operator-token").expect("operator");
    let runtime = operator
        .request(envelope("runtime", ControlRequest::Runtime))
        .await;
    let Ok(ControlPayload::Runtime(runtime)) = runtime.result else {
        panic!("runtime projection")
    };
    assert_eq!(runtime.region_name.as_deref(), Some("Test Region"));
    assert_eq!(runtime.region_handle, Some(9_007_199_254_740_992));
    assert_eq!(runtime.control_queue_capacity, 8);
    assert_eq!(runtime.active_visual_capture.as_deref(), Some("visual-1"));
    assert_eq!(runtime.visual_progress.as_deref(), Some("rendering"));
    assert_eq!(runtime.visual_image_sha256.as_deref(), Some("abc123"));
    let metrics = operator
        .request(envelope("metrics", ControlRequest::Metrics))
        .await;
    let Ok(ControlPayload::Metrics(metrics)) = metrics.result else {
        panic!("metrics projection")
    };
    assert_eq!(metrics.queue_capacity, 8);

    assert!(
        operator
            .request(envelope("pause", ControlRequest::PauseAutonomy))
            .await
            .result
            .is_ok()
    );
    assert!(matches!(
        commands.recv().await,
        Some(RuntimeControlCommand::Pause)
    ));
    assert!(vision.active_capture().is_none(), "pause cancels capture");

    let observer = plane.connect("observer-token").expect("observer");
    let denied = observer
        .request(envelope("shutdown", ControlRequest::GracefulShutdown))
        .await;
    assert!(denied.result.is_err());
    assert!(commands.try_recv().is_err());
    let events = observer
        .request(envelope(
            "events",
            ControlRequest::ListObservabilityEvents {
                page: PageRequest {
                    cursor: None,
                    limit: 100,
                },
            },
        ))
        .await;
    let Ok(ControlPayload::ObservabilityEvents(events)) = events.result else {
        panic!("observability events")
    };
    assert!(
        events
            .items
            .iter()
            .any(|event| event.family == "control_command")
    );
    assert!(events.items.iter().all(|event| {
        !serde_json::to_string(event)
            .expect("event JSON")
            .contains("operator-token")
    }));
    behavior.shutdown().await.expect("shutdown behavior");
}
