use std::sync::{Arc, Mutex};

use application::{
    ResolveAgentSystemPermissions, ResolveAgentSystemPermissionsInput,
    UpdateAgentSystemPermissions, UpdateAgentSystemPermissionsInput,
    UpdateProjectSystemPermissions, UpdateProjectSystemPermissionsInput,
};
use async_trait::async_trait;
use domain::ids::{AgentId, ProjectId};
use domain::ports::{RuntimeError, RuntimePermissionProbe, StoreError, SystemPermissionStore};
use domain::project::{Project, ProjectPath};
use domain::remote::RemoteRef;
use domain::{
    NetworkPolicy, ProjectSystemPermissions, RuntimeLockState, RuntimePermissionSnapshot,
    SystemPermissionControlMode, SystemPermissionSet,
};

#[derive(Default)]
struct FakeSystemPermissionStore {
    doc: Mutex<ProjectSystemPermissions>,
    saves: Mutex<usize>,
}

#[async_trait]
impl SystemPermissionStore for FakeSystemPermissionStore {
    async fn load_system_permissions(
        &self,
        _project: &Project,
    ) -> Result<ProjectSystemPermissions, StoreError> {
        Ok(self.doc.lock().unwrap().clone())
    }

    async fn save_system_permissions(
        &self,
        _project: &Project,
        permissions: &ProjectSystemPermissions,
    ) -> Result<(), StoreError> {
        *self.doc.lock().unwrap() = permissions.clone();
        *self.saves.lock().unwrap() += 1;
        Ok(())
    }
}

struct LockedProbe;

#[async_trait]
impl RuntimePermissionProbe for LockedProbe {
    async fn probe_runtime_permissions(
        &self,
        _project: &Project,
        _agent_id: AgentId,
    ) -> Result<RuntimePermissionSnapshot, RuntimeError> {
        Ok(RuntimePermissionSnapshot::locked_uninspectable())
    }
}

struct UnobservedProbe;

#[async_trait]
impl RuntimePermissionProbe for UnobservedProbe {
    async fn probe_runtime_permissions(
        &self,
        _project: &Project,
        _agent_id: AgentId,
    ) -> Result<RuntimePermissionSnapshot, RuntimeError> {
        Ok(RuntimePermissionSnapshot::unobserved())
    }
}

fn project() -> Project {
    Project::new(
        ProjectId::new_random(),
        "system-permissions",
        ProjectPath::new("/home/me/proj").unwrap(),
        RemoteRef::local(),
        1_700_000_000_000,
    )
    .unwrap()
}

#[tokio::test]
async fn update_project_system_permissions_replaces_defaults_and_persists() {
    let store = Arc::new(FakeSystemPermissionStore::default());
    let use_case = UpdateProjectSystemPermissions::new(store.clone());

    let out = use_case
        .execute(UpdateProjectSystemPermissionsInput {
            project: project(),
            permissions: Some(SystemPermissionSet::new(Some(NetworkPolicy::Ask))),
        })
        .await
        .unwrap();

    assert_eq!(
        out.permissions.project_default.unwrap().network,
        Some(NetworkPolicy::Ask)
    );
    assert_eq!(*store.saves.lock().unwrap(), 1);
}

#[tokio::test]
async fn update_agent_system_permissions_adds_and_removes_sparse_override() {
    let store = Arc::new(FakeSystemPermissionStore::default());
    let use_case = UpdateAgentSystemPermissions::new(store.clone());
    let agent = AgentId::new_random();

    use_case
        .execute(UpdateAgentSystemPermissionsInput {
            project: project(),
            agent_id: agent,
            permissions: Some(SystemPermissionSet::new(Some(NetworkPolicy::Deny))),
        })
        .await
        .unwrap();

    assert_eq!(
        store
            .doc
            .lock()
            .unwrap()
            .agent_permissions(agent)
            .unwrap()
            .network,
        Some(NetworkPolicy::Deny)
    );

    let out = use_case
        .execute(UpdateAgentSystemPermissionsInput {
            project: project(),
            agent_id: agent,
            permissions: None,
        })
        .await
        .unwrap();

    assert!(out.permissions.agent_permissions(agent).is_none());
    assert_eq!(*store.saves.lock().unwrap(), 2);
}

#[tokio::test]
async fn resolve_agent_system_permissions_exposes_locked_runtime_separately_from_wanted_control() {
    let agent = AgentId::new_random();
    let store = Arc::new(FakeSystemPermissionStore {
        doc: Mutex::new(ProjectSystemPermissions::new(
            Some(SystemPermissionSet::new(Some(NetworkPolicy::Allow))),
            vec![],
        )),
        saves: Mutex::new(0),
    });
    let use_case = ResolveAgentSystemPermissions::new(store, Arc::new(LockedProbe));

    let out = use_case
        .execute(ResolveAgentSystemPermissionsInput {
            project: project(),
            agent_id: agent,
        })
        .await
        .unwrap();

    assert_eq!(out.permissions.wanted, Some(NetworkPolicy::Allow));
    assert_eq!(out.permissions.effective, NetworkPolicy::Deny);
    assert_eq!(out.permissions.runtime_lock.state, RuntimeLockState::Locked);
    assert_eq!(
        out.permissions.control.mode,
        SystemPermissionControlMode::Editable
    );
    assert_eq!(
        out.permissions.runtime_control.mode,
        SystemPermissionControlMode::ReadOnly
    );
}

#[tokio::test]
async fn resolve_agent_system_permissions_unobserved_runtime_does_not_lock_form() {
    let agent = AgentId::new_random();
    let store = Arc::new(FakeSystemPermissionStore {
        doc: Mutex::new(ProjectSystemPermissions::new(
            Some(SystemPermissionSet::new(Some(NetworkPolicy::Allow))),
            vec![],
        )),
        saves: Mutex::new(0),
    });
    let use_case = ResolveAgentSystemPermissions::new(store, Arc::new(UnobservedProbe));

    let out = use_case
        .execute(ResolveAgentSystemPermissionsInput {
            project: project(),
            agent_id: agent,
        })
        .await
        .unwrap();

    assert_eq!(out.permissions.wanted, Some(NetworkPolicy::Allow));
    assert_eq!(out.permissions.effective, NetworkPolicy::Allow);
    assert_eq!(out.permissions.runtime_lock.state, RuntimeLockState::None);
    assert_eq!(
        out.permissions.control.mode,
        SystemPermissionControlMode::Editable
    );
    assert_eq!(
        out.permissions.runtime_control.mode,
        SystemPermissionControlMode::ReadOnly
    );
}
