//! Group management domain logic — metadata, membership tracking.

use std::sync::Arc;

use crate::storage::Store;

use super::types::*;

/// Domain service for group metadata and membership.
pub struct GroupService {
    pub store: Arc<dyn Store>,
}

impl GroupService {
    /// Update group metadata (name, description, avatar_hash).
    pub fn update_metadata(
        &self,
        req: UpdateGroupMetadataReq,
        caller_identity_key: &[u8],
    ) -> Result<(), DomainError> {
        if req.group_id.is_empty() {
            return Err(DomainError::BadParams("group_id must not be empty".into()));
        }

        self.store.store_group_metadata(
            &req.group_id,
            &req.name,
            &req.description,
            &req.avatar_hash,
            caller_identity_key,
        )?;

        Ok(())
    }

    /// List group members with resolved usernames.
    pub fn list_members(
        &self,
        req: ListGroupMembersReq,
    ) -> Result<ListGroupMembersResp, DomainError> {
        if req.group_id.is_empty() {
            return Err(DomainError::BadParams("group_id must not be empty".into()));
        }

        let raw = self.store.list_group_members(&req.group_id)?;
        let members = raw
            .into_iter()
            .map(|(identity_key, joined_at)| {
                let username = self
                    .store
                    .resolve_identity_key(&identity_key)
                    .ok()
                    .flatten()
                    .unwrap_or_default();
                GroupMemberInfo {
                    identity_key,
                    username,
                    joined_at,
                }
            })
            .collect();
        Ok(ListGroupMembersResp { members })
    }

    /// Track a member addition in the server-side membership table.
    pub fn add_member(
        &self,
        group_id: &[u8],
        identity_key: &[u8],
    ) -> Result<(), DomainError> {
        self.store.add_group_member(group_id, identity_key)?;
        Ok(())
    }

    /// Track a member removal in the server-side membership table.
    pub fn remove_member(
        &self,
        group_id: &[u8],
        identity_key: &[u8],
    ) -> Result<bool, DomainError> {
        let removed = self.store.remove_group_member(group_id, identity_key)?;
        Ok(removed)
    }

    /// Get group metadata.
    pub fn get_metadata(
        &self,
        group_id: &[u8],
    ) -> Result<Option<GroupMetadata>, DomainError> {
        if group_id.is_empty() {
            return Err(DomainError::BadParams("group_id must not be empty".into()));
        }

        match self.store.get_group_metadata(group_id)? {
            Some((name, description, avatar_hash, creator_key, created_at)) => {
                Ok(Some(GroupMetadata {
                    group_id: group_id.to_vec(),
                    name,
                    description,
                    avatar_hash,
                    creator_key,
                    created_at,
                }))
            }
            None => Ok(None),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::storage::FileBackedStore;

    fn make_service() -> (GroupService, tempfile::TempDir) {
        let dir = tempfile::tempdir().expect("tempdir");
        let store = FileBackedStore::open(dir.path()).expect("open store");
        let svc = GroupService {
            store: Arc::new(store),
        };
        (svc, dir)
    }

    #[test]
    fn update_and_get_metadata() {
        let (svc, _dir) = make_service();
        let group_id = b"test-group-00001".to_vec();
        let caller = b"caller-key".to_vec();

        svc.update_metadata(
            UpdateGroupMetadataReq {
                group_id: group_id.clone(),
                name: "Test Group".into(),
                description: "A test group".into(),
                avatar_hash: vec![0xAB],
            },
            &caller,
        )
        .expect("update_metadata should succeed");

        let meta = svc.get_metadata(&group_id).expect("get_metadata should succeed");
        let meta = meta.expect("metadata should exist");
        assert_eq!(meta.name, "Test Group");
        assert_eq!(meta.description, "A test group");
        assert_eq!(meta.avatar_hash, vec![0xAB]);
        assert_eq!(meta.creator_key, caller);
    }

    #[test]
    fn get_metadata_nonexistent_returns_none() {
        let (svc, _dir) = make_service();
        let result = svc.get_metadata(b"no-such-group").expect("should not error");
        assert!(result.is_none());
    }

    #[test]
    fn empty_group_id_rejected() {
        let (svc, _dir) = make_service();
        let err = svc.update_metadata(
            UpdateGroupMetadataReq {
                group_id: Vec::new(),
                name: "X".into(),
                description: String::new(),
                avatar_hash: Vec::new(),
            },
            b"caller",
        );
        assert!(err.is_err());
    }

    #[test]
    fn add_list_remove_members() {
        let (svc, _dir) = make_service();
        let group_id = b"membership-group".to_vec();
        let member_a = b"member-a-key".to_vec();
        let member_b = b"member-b-key".to_vec();

        svc.add_member(&group_id, &member_a).expect("add a");
        svc.add_member(&group_id, &member_b).expect("add b");

        let resp = svc
            .list_members(ListGroupMembersReq {
                group_id: group_id.clone(),
            })
            .expect("list members");
        assert_eq!(resp.members.len(), 2);

        let removed = svc.remove_member(&group_id, &member_a).expect("remove a");
        assert!(removed);

        let resp = svc
            .list_members(ListGroupMembersReq {
                group_id: group_id.clone(),
            })
            .expect("list members after removal");
        assert_eq!(resp.members.len(), 1);
        assert_eq!(resp.members[0].identity_key, member_b);
    }

    #[test]
    fn remove_nonexistent_member_returns_false() {
        let (svc, _dir) = make_service();
        let removed = svc
            .remove_member(b"group", b"nobody")
            .expect("remove nonexistent");
        assert!(!removed);
    }
}
