//! Per-namespace trust lists.
//!
//! Controls which peers' cached results are accepted. When a trust list
//! exists for a namespace, only entries from listed author IDs are used.
//! An empty or absent trust list means "trust everyone" (open namespace).
//!
//! Use cases:
//!   - Institution namespaces: only trust peers from your lab/org
//!   - Reproducibility: pin trusted author set for a publication
//!   - Restricted networks: combine with bootstrap_peers for private meshes
#![allow(dead_code)] // scaffolding: wired in when gossip messages carry author IDs

use std::collections::HashSet;
use std::path::Path;

use anyhow::{Context, Result};

pub struct TrustList {
    /// None = open (trust everyone). Some(set) = only trust listed IDs.
    authors: Option<HashSet<String>>,
}

impl TrustList {
    /// Load from `<ns_dir>/trusted_authors.txt` (one hex node ID per line).
    /// Missing file = open trust. Empty file = trust nobody.
    pub fn load(ns_dir: &Path) -> Result<Self> {
        let path = ns_dir.join("trusted_authors.txt");
        if !path.exists() {
            return Ok(Self { authors: None });
        }
        let text = std::fs::read_to_string(&path).context("read trust list")?;
        let authors: HashSet<String> = text
            .lines()
            .map(|l| l.trim().to_string())
            .filter(|l| !l.is_empty() && !l.starts_with('#'))
            .collect();
        Ok(Self { authors: Some(authors) })
    }

    /// Check if a peer's node ID is trusted in this namespace.
    pub fn is_trusted(&self, author_id: &str) -> bool {
        match &self.authors {
            None => true,
            Some(set) => set.contains(author_id),
        }
    }

    /// Add a peer to the trust list (creates the list if open).
    pub fn add(&mut self, author_id: &str) {
        self.authors
            .get_or_insert_with(HashSet::new)
            .insert(author_id.to_string());
    }

    /// Save trust list back to disk.
    pub fn save(&self, ns_dir: &Path) -> Result<()> {
        let path = ns_dir.join("trusted_authors.txt");
        match &self.authors {
            None => {
                // Open trust: remove file if it exists
                let _ = std::fs::remove_file(&path);
            }
            Some(set) => {
                let mut lines: Vec<&str> = set.iter().map(|s| s.as_str()).collect();
                lines.sort();
                std::fs::write(&path, lines.join("\n") + "\n")?;
            }
        }
        Ok(())
    }
}

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

    #[test]
    fn empty_trust_list_trusts_everyone() {
        let t = TrustList { authors: None };
        assert!(t.is_trusted("anyone"));
    }

    #[test]
    fn populated_trust_list_only_trusts_listed() {
        let mut t = TrustList { authors: None };
        t.add("alice");
        assert!(t.is_trusted("alice"));
        assert!(!t.is_trusted("bob"));
    }

    #[test]
    fn load_nonexistent_is_open() {
        let dir = tempfile::tempdir().unwrap();
        let t = TrustList::load(dir.path()).unwrap();
        assert!(t.is_trusted("anyone"));
    }

    #[test]
    fn load_from_file() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(
            dir.path().join("trusted_authors.txt"),
            "alice\n# comment\nbob\n",
        )
        .unwrap();
        let t = TrustList::load(dir.path()).unwrap();
        assert!(t.is_trusted("alice"));
        assert!(t.is_trusted("bob"));
        assert!(!t.is_trusted("eve"));
    }

    #[test]
    fn save_roundtrip() {
        let dir = tempfile::tempdir().unwrap();
        let mut t = TrustList { authors: None };
        t.add("alice");
        t.add("bob");
        t.save(dir.path()).unwrap();

        let loaded = TrustList::load(dir.path()).unwrap();
        assert!(loaded.is_trusted("alice"));
        assert!(loaded.is_trusted("bob"));
        assert!(!loaded.is_trusted("eve"));
    }
}
