//! AdmissionStore implementation backed by TrustRankStore.
//!
//! This module provides the concrete implementation of AdmissionStore operations.
//! It wraps the TrustRankStore to leverage existing trust score and assertion count
//! data for admission control decisions.

use crate::error::Result;
use crate::trust_rank_store::TrustRankStore;
use async_trait::async_trait;
use stemedb_core::types::{AdmissionConfig, PowError, PowProof};
use tracing::{debug, instrument};

use super::model::AdmissionStatus;
use super::AdmissionStore;

/// AdmissionStore implementation backed by TrustRankStore.
///
/// This implementation wraps an existing TrustRankStore and computes admission
/// decisions based on the agent's trust score and assertion count.
///
/// # Design Decision
///
/// Rather than creating new storage keys, this implementation reuses the existing
/// TrustRank data structure which already tracks:
/// - `score: f32` - Maps to TrustTier
/// - `assertions_count: u64` - Used for PoW graduation
///
/// This means admission control is automatically integrated with the reputation
/// system and no schema migration is needed.
#[derive(Clone)]
pub struct GenericAdmissionStore<T> {
    trust_store: T,
    config: AdmissionConfig,
}

impl<T: TrustRankStore> GenericAdmissionStore<T> {
    /// Create a new AdmissionStore backed by the given TrustRankStore.
    ///
    /// Uses default admission configuration.
    pub fn new(trust_store: T) -> Self {
        Self { trust_store, config: AdmissionConfig::default() }
    }

    /// Create a new AdmissionStore with custom configuration.
    pub fn with_config(trust_store: T, config: AdmissionConfig) -> Self {
        Self { trust_store, config }
    }
}

#[async_trait]
impl<T: TrustRankStore + 'static> AdmissionStore for GenericAdmissionStore<T> {
    #[instrument(skip(self), fields(agent_id = %hex::encode(agent_id)))]
    async fn get_admission_status(&self, agent_id: &[u8; 32]) -> Result<AdmissionStatus> {
        let trust_rank = self.trust_store.get_trust_rank(agent_id).await?;

        let difficulty =
            self.config.compute_difficulty(trust_rank.assertions_count, trust_rank.score);

        let status =
            AdmissionStatus::new(trust_rank.score, trust_rank.assertions_count, difficulty);

        debug!(
            tier = %status.tier,
            trust_score = status.trust_score,
            assertions_count = status.assertions_count,
            pow_difficulty = status.pow_difficulty,
            pow_required = status.pow_required,
            "Retrieved admission status"
        );

        Ok(status)
    }

    #[instrument(skip(self, proof), fields(
        nonce = proof.nonce,
        timestamp = proof.timestamp,
        required_difficulty
    ))]
    async fn verify_pow(
        &self,
        proof: &PowProof,
        required_difficulty: u8,
        server_time: u64,
    ) -> Result<std::result::Result<(), PowError>> {
        // If difficulty is 0, no verification needed
        if required_difficulty == 0 {
            debug!("PoW exempt (difficulty 0)");
            return Ok(Ok(()));
        }

        // Verify the proof
        let result = proof.verify(required_difficulty, self.config.pow_max_age, server_time);

        match &result {
            Ok(()) => {
                debug!("PoW verified successfully");
            }
            Err(e) => {
                debug!(error = %e, "PoW verification failed");
            }
        }

        Ok(result)
    }

    #[instrument(skip(self), fields(agent_id = %hex::encode(agent_id)))]
    async fn compute_difficulty(&self, agent_id: &[u8; 32]) -> Result<u8> {
        let trust_rank = self.trust_store.get_trust_rank(agent_id).await?;
        let difficulty =
            self.config.compute_difficulty(trust_rank.assertions_count, trust_rank.score);

        debug!(
            assertions_count = trust_rank.assertions_count,
            trust_score = trust_rank.score,
            difficulty,
            "Computed PoW difficulty"
        );

        Ok(difficulty)
    }

    #[instrument(skip(self), fields(agent_id = %hex::encode(agent_id), timestamp))]
    async fn record_assertion(&self, agent_id: &[u8; 32], timestamp: u64) -> Result<u64> {
        // Get current trust rank
        let mut trust_rank = self.trust_store.get_trust_rank(agent_id).await?;

        // Increment assertion count
        let old_count = trust_rank.assertions_count;
        trust_rank.assertions_count = trust_rank.assertions_count.saturating_add(1);
        trust_rank.last_updated = timestamp;

        // Store updated trust rank
        self.trust_store.put_trust_rank(&trust_rank).await?;

        debug!(
            old_count,
            new_count = trust_rank.assertions_count,
            "Recorded assertion for admission tracking"
        );

        Ok(trust_rank.assertions_count)
    }

    fn config(&self) -> &AdmissionConfig {
        &self.config
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::trust_rank_store::{TrustAdjustment, TrustRank};
    use std::collections::HashMap;
    use std::sync::Mutex;

    /// Mock TrustRankStore for testing.
    struct MockTrustRankStore {
        ranks: Mutex<HashMap<[u8; 32], TrustRank>>,
    }

    impl MockTrustRankStore {
        fn new() -> Self {
            Self { ranks: Mutex::new(HashMap::new()) }
        }

        fn set_rank(&self, rank: TrustRank) {
            self.ranks.lock().expect("lock").insert(rank.agent_id, rank);
        }
    }

    #[async_trait]
    impl TrustRankStore for MockTrustRankStore {
        async fn get_trust_rank(&self, agent_id: &[u8; 32]) -> Result<TrustRank> {
            let ranks = self.ranks.lock().expect("lock");
            Ok(ranks.get(agent_id).cloned().unwrap_or_else(|| TrustRank::new(*agent_id, 0)))
        }

        async fn update_trust_rank(
            &self,
            _agent_id: &[u8; 32],
            _delta: f32,
            _timestamp: u64,
        ) -> Result<f32> {
            unimplemented!("not needed for admission tests")
        }

        async fn decay_trust_ranks(
            &self,
            _current_timestamp: u64,
            _half_life_seconds: Option<u64>,
        ) -> Result<usize> {
            unimplemented!("not needed for admission tests")
        }

        async fn record_outcome(
            &self,
            _agent_id: &[u8; 32],
            _was_accurate: bool,
            _timestamp: u64,
        ) -> Result<f32> {
            unimplemented!("not needed for admission tests")
        }

        async fn put_trust_rank(&self, trust_rank: &TrustRank) -> Result<()> {
            let mut ranks = self.ranks.lock().expect("lock");
            ranks.insert(trust_rank.agent_id, trust_rank.clone());
            Ok(())
        }

        async fn verify_agent_against_gold_standard(
            &self,
            _agent_id: &[u8; 32],
            _agent_object: &str,
            _gold_standard: &stemedb_core::types::GoldStandard,
            _timestamp: u64,
        ) -> Result<TrustAdjustment> {
            unimplemented!("not needed for admission tests")
        }
    }

    #[tokio::test]
    async fn test_new_agent_requires_pow() {
        let trust_store = MockTrustRankStore::new();
        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [1u8; 32];
        let status = admission_store.get_admission_status(&agent_id).await.expect("get status");

        // New agent: default trust 0.5, 0 assertions
        assert!((status.trust_score - 0.5).abs() < f32::EPSILON);
        assert_eq!(status.assertions_count, 0);

        // Should require PoW with initial difficulty
        assert!(status.pow_required);
        assert_eq!(status.pow_difficulty, 16);
    }

    #[tokio::test]
    async fn test_graduated_agent_no_pow() {
        let trust_store = MockTrustRankStore::new();

        // Set up an agent with 50+ assertions
        let mut rank = TrustRank::new([2u8; 32], 0);
        rank.assertions_count = 50;
        rank.score = 0.4; // Low trust but high assertion count
        trust_store.set_rank(rank);

        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [2u8; 32];
        let status = admission_store.get_admission_status(&agent_id).await.expect("get status");

        // 50+ assertions = exempt
        assert!(!status.pow_required);
        assert_eq!(status.pow_difficulty, 0);
    }

    #[tokio::test]
    async fn test_trusted_agent_no_pow() {
        let trust_store = MockTrustRankStore::new();

        // Set up an agent with high trust
        let mut rank = TrustRank::new([3u8; 32], 0);
        rank.score = 0.7; // High trust
        rank.assertions_count = 5; // Few assertions
        trust_store.set_rank(rank);

        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [3u8; 32];
        let status = admission_store.get_admission_status(&agent_id).await.expect("get status");

        // High trust = exempt (trust_exemption_score is 0.6 by default)
        assert!(!status.pow_required);
        assert_eq!(status.pow_difficulty, 0);
    }

    #[tokio::test]
    async fn test_reduced_difficulty_after_10_assertions() {
        let trust_store = MockTrustRankStore::new();

        // Set up an agent with 10-49 assertions
        let mut rank = TrustRank::new([4u8; 32], 0);
        rank.assertions_count = 15;
        rank.score = 0.4; // Low trust
        trust_store.set_rank(rank);

        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [4u8; 32];
        let status = admission_store.get_admission_status(&agent_id).await.expect("get status");

        // 10-49 assertions = reduced difficulty (1 bit)
        assert!(status.pow_required);
        assert_eq!(status.pow_difficulty, 1);
    }

    #[tokio::test]
    async fn test_verify_pow_exempt() {
        let trust_store = MockTrustRankStore::new();
        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [5u8; 32];
        let proof = PowProof::new(0, agent_id, 1700000000);

        // Difficulty 0 = always passes - just verify it doesn't error
        admission_store.verify_pow(&proof, 0, 1700000000).await.expect("verify").expect("ok");
    }

    #[tokio::test]
    async fn test_verify_pow_valid() {
        let trust_store = MockTrustRankStore::new();
        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [6u8; 32];
        let timestamp = 1700000000u64;

        // Solve a real puzzle (low difficulty for fast test)
        let proof = PowProof::solve(agent_id, timestamp, 4);

        // Should verify
        let result = admission_store.verify_pow(&proof, 4, timestamp).await.expect("verify");
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_verify_pow_expired() {
        let trust_store = MockTrustRankStore::new();
        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [7u8; 32];
        let old_timestamp = 1000u64;
        let server_time = 2000u64; // 1000 seconds later

        let proof = PowProof::new(0, agent_id, old_timestamp);

        // Should fail due to expired timestamp
        let result = admission_store.verify_pow(&proof, 1, server_time).await.expect("verify");
        assert!(matches!(result, Err(PowError::TimestampExpired { .. })));
    }

    #[tokio::test]
    async fn test_record_assertion() {
        let trust_store = MockTrustRankStore::new();
        let admission_store = GenericAdmissionStore::new(trust_store);

        let agent_id = [8u8; 32];
        let timestamp = 1700000000u64;

        // Record first assertion
        let count = admission_store.record_assertion(&agent_id, timestamp).await.expect("record");
        assert_eq!(count, 1);

        // Record second assertion
        let count =
            admission_store.record_assertion(&agent_id, timestamp + 1).await.expect("record");
        assert_eq!(count, 2);

        // Check status
        let status = admission_store.get_admission_status(&agent_id).await.expect("get status");
        assert_eq!(status.assertions_count, 2);
    }

    #[tokio::test]
    async fn test_tier_affects_quota() {
        let trust_store = MockTrustRankStore::new();

        // Authority tier agent
        let mut rank = TrustRank::new([9u8; 32], 0);
        rank.score = 0.95;
        trust_store.set_rank(rank);

        let admission_store = GenericAdmissionStore::new(trust_store);

        let status = admission_store.get_admission_status(&[9u8; 32]).await.expect("get status");

        assert_eq!(status.tier, stemedb_core::types::TrustTier::Authority);
        assert_eq!(status.effective_quota_limit, 100_000);
        assert!((status.quota_multiplier - 10.0).abs() < f32::EPSILON);
    }
}
