use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use blake3::Hasher;

/// Represents a fuzzy time range for transaction timestamps
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TimeRange {
    /// Earliest possible timestamp (Unix seconds)
    pub earliest: i64,
    /// Latest possible timestamp (Unix seconds)
    pub latest: i64,
}

impl TimeRange {
    /// Minimum allowed range duration (2 hours)
    pub const MIN_RANGE_SECONDS: i64 = 2 * 3600;
    /// Maximum allowed range duration (6 hours)
    pub const MAX_RANGE_SECONDS: i64 = 6 * 3600;
    /// Maximum age for a transaction (4 hours from latest)
    pub const MAX_TX_AGE_SECONDS: i64 = 4 * 3600;

    /// Create a new TimeRange around a real timestamp
    pub fn new(real_time: DateTime<Utc>, range_hours: u32) -> Result<Self, TimeRangeError> {
        let range_seconds = (range_hours as i64) * 3600;

        if range_seconds < Self::MIN_RANGE_SECONDS {
            return Err(TimeRangeError::RangeTooSmall);
        }

        if range_seconds > Self::MAX_RANGE_SECONDS {
            return Err(TimeRangeError::RangeTooLarge);
        }

        let half_range = Duration::seconds(range_seconds / 2);
        let earliest = (real_time - half_range).timestamp();
        let latest = (real_time + half_range).timestamp();

        Ok(Self { earliest, latest })
    }

    /// Create a fuzzy range with random padding
    pub fn new_fuzzy(real_time: DateTime<Utc>) -> Self {
        use rand::Rng;
        let mut rng = rand::thread_rng();

        // Random range between 2-6 hours
        let range_hours = rng.gen_range(2..=6);

        Self::new(real_time, range_hours).expect("Range should be valid")
    }

    /// Check if this range is valid at current time
    pub fn is_valid(&self, current_time: DateTime<Utc>) -> bool {
        let range_duration = self.latest - self.earliest;

        // Check range size
        if range_duration < Self::MIN_RANGE_SECONDS || range_duration > Self::MAX_RANGE_SECONDS {
            return false;
        }

        // Check not too old
        let age = current_time.timestamp() - self.latest;
        if age > Self::MAX_TX_AGE_SECONDS {
            return false;
        }

        true
    }

    /// Get the duration of this range in seconds
    pub fn duration_seconds(&self) -> i64 {
        self.latest - self.earliest
    }

    /// Check if a timestamp falls within this range
    pub fn contains(&self, timestamp: i64) -> bool {
        timestamp >= self.earliest && timestamp <= self.latest
    }
}

/// Zero-knowledge proof that a transaction was created within a time range
/// Simplified version using hash chains
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeRangeProof {
    /// Hash at earliest time point
    pub earliest_hash: [u8; 32],
    /// Hash at latest time point
    pub latest_hash: [u8; 32],
    /// Commitment to the real creation time
    pub time_commitment: [u8; 32],
}

impl TimeRangeProof {
    /// Generate a proof for a real timestamp within a range
    /// Uses hash chain: H^n(seed) where n = minutes since epoch
    pub fn generate(
        seed: &[u8; 32],
        real_time: DateTime<Utc>,
        time_range: &TimeRange,
    ) -> Result<Self, TimeRangeError> {
        if !time_range.contains(real_time.timestamp()) {
            return Err(TimeRangeError::TimeOutOfRange);
        }

        // Convert timestamps to "minutes since epoch" for hash chain
        let earliest_minutes = time_range.earliest / 60;
        let latest_minutes = time_range.latest / 60;
        let real_minutes = real_time.timestamp() / 60;

        // Generate hash chain values
        let earliest_hash = Self::hash_chain(seed, earliest_minutes as u64);
        let latest_hash = Self::hash_chain(seed, latest_minutes as u64);

        // Commitment to real time (hash of seed + real_time)
        let mut hasher = Hasher::new();
        hasher.update(seed);
        hasher.update(&real_minutes.to_le_bytes());
        let time_commitment = *hasher.finalize().as_bytes();

        Ok(Self {
            earliest_hash,
            latest_hash,
            time_commitment,
        })
    }

    /// Verify that the proof is internally consistent
    /// Note: This is a simplified proof system. A production version would use
    /// proper ZK-SNARKs or Bulletproofs for stronger guarantees
    pub fn verify(&self, time_range: &TimeRange) -> bool {
        // In a real implementation, we would verify:
        // 1. earliest_hash and latest_hash are valid points in hash chain
        // 2. time_commitment corresponds to a point between them
        // 3. No information about real_time is leaked

        // For this prototype, we just check the hashes are non-zero
        self.earliest_hash != [0u8; 32]
            && self.latest_hash != [0u8; 32]
            && self.time_commitment != [0u8; 32]
            && time_range.duration_seconds() > 0
    }

    /// Hash chain computation: applies blake3 hash n times
    fn hash_chain(seed: &[u8; 32], iterations: u64) -> [u8; 32] {
        let mut current = *seed;

        for _ in 0..iterations {
            let mut hasher = Hasher::new();
            hasher.update(&current);
            current = *hasher.finalize().as_bytes();
        }

        current
    }
}

#[derive(Debug, thiserror::Error)]
pub enum TimeRangeError {
    #[error("Time range is too small (minimum 2 hours)")]
    RangeTooSmall,

    #[error("Time range is too large (maximum 6 hours)")]
    RangeTooLarge,

    #[error("Real timestamp is outside the specified range")]
    TimeOutOfRange,
}

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

    #[test]
    fn test_timerange_creation() {
        let now = Utc::now();
        let range = TimeRange::new(now, 4).unwrap();

        assert!(range.duration_seconds() == 4 * 3600);
        assert!(range.contains(now.timestamp()));
    }

    #[test]
    fn test_timerange_validation() {
        let now = Utc::now();
        let range = TimeRange::new(now, 4).unwrap();

        assert!(range.is_valid(now));
        assert!(range.is_valid(now + Duration::hours(1)));
    }

    #[test]
    fn test_timerange_proof() {
        let seed = [42u8; 32];
        let now = Utc::now();
        let range = TimeRange::new(now, 4).unwrap();

        let proof = TimeRangeProof::generate(&seed, now, &range).unwrap();
        assert!(proof.verify(&range));
    }

    #[test]
    fn test_fuzzy_range() {
        let now = Utc::now();
        let range1 = TimeRange::new_fuzzy(now);
        let range2 = TimeRange::new_fuzzy(now);

        // Ranges should be valid but potentially different
        assert!(range1.is_valid(now));
        assert!(range2.is_valid(now));
    }
}
