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

use application::{
    ResolveAgentPermissions, ResolveAgentPermissionsInput, UpdateAgentPermissions,
    UpdateAgentPermissionsInput, UpdateProjectPermissions, UpdateProjectPermissionsInput,
};
use async_trait::async_trait;
use domain::ids::{AgentId, ProjectId};
use domain::ports::{PermissionStore, StoreError};
use domain::project::{Project, ProjectPath};
use domain::remote::RemoteRef;
use domain::{
    Capability, Effect, PermissionRule, PermissionSet, PermissionShadowReport, Posture,
    ProjectPermissions,
};

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

#[async_trait]
impl PermissionStore for FakePermissionStore {
    async fn load_permissions(&self, _project: &Project) -> Result<ProjectPermissions, StoreError> {
        Ok(self.doc.lock().unwrap().clone())
    }

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

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

#[tokio::test]
async fn update_project_permissions_replaces_defaults_and_persists() {
    let store = Arc::new(FakePermissionStore::default());
    let use_case = UpdateProjectPermissions::new(store.clone());

    let out = use_case
        .execute(UpdateProjectPermissionsInput {
            project: project(),
            permissions: Some(PermissionSet::new(vec![], Posture::Ask)),
        })
        .await
        .unwrap();

    assert_eq!(
        out.permissions.project_defaults.unwrap().fallback(),
        Posture::Ask
    );
    assert_eq!(*store.saves.lock().unwrap(), 1);
}

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

    use_case
        .execute(UpdateAgentPermissionsInput {
            project: project(),
            agent_id: agent,
            permissions: Some(PermissionSet::new(vec![], Posture::Deny)),
        })
        .await
        .unwrap();

    assert_eq!(
        store
            .doc
            .lock()
            .unwrap()
            .agent_permissions(agent)
            .unwrap()
            .fallback(),
        Posture::Deny
    );

    let out = use_case
        .execute(UpdateAgentPermissionsInput {
            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_permissions_returns_effective_policy() {
    let agent = AgentId::new_random();
    let store = Arc::new(FakePermissionStore {
        doc: Mutex::new(ProjectPermissions::new(
            Some(PermissionSet::new(vec![], Posture::Ask)),
            vec![],
        )),
        saves: Mutex::new(0),
    });
    let use_case = ResolveAgentPermissions::new(store);

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

    assert_eq!(out.effective.unwrap().fallback(), Posture::Ask);
    assert_eq!(out.shadowed, PermissionShadowReport::default());
}

#[tokio::test]
async fn resolve_agent_permissions_reports_shadowed_alongside_unchanged_effective() {
    let agent = AgentId::new_random();
    let store = Arc::new(FakePermissionStore {
        doc: Mutex::new(ProjectPermissions::new(
            Some(PermissionSet::new(
                vec![PermissionRule::bash(Effect::Deny, vec![])],
                Posture::Ask,
            )),
            vec![domain::AgentPermissionOverride::new(
                agent,
                PermissionSet::new(
                    vec![PermissionRule::bash(Effect::Allow, vec![])],
                    Posture::Ask,
                ),
            )],
        )),
        saves: Mutex::new(0),
    });
    let use_case = ResolveAgentPermissions::new(store);

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

    assert!(out.shadowed.execute_bash);
    assert_eq!(out.effective.unwrap().decide_bash("ls"), Posture::Deny);
}

#[tokio::test]
async fn resolve_agent_permissions_shadowed_defaults_when_no_agent_override() {
    let agent = AgentId::new_random();
    let store = Arc::new(FakePermissionStore {
        doc: Mutex::new(ProjectPermissions::new(
            Some(PermissionSet::new(
                vec![PermissionRule::file(
                    Capability::Read,
                    Effect::Deny,
                    domain::PathScope::new(["**".to_owned()]).unwrap(),
                )
                .unwrap()],
                Posture::Deny,
            )),
            vec![],
        )),
        saves: Mutex::new(0),
    });
    let use_case = ResolveAgentPermissions::new(store);

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

    assert_eq!(out.shadowed, PermissionShadowReport::default());
}
