use std::{
    collections::{HashMap, VecDeque},
    hash::{Hash, Hasher},
};

use lanspread_proto::{GameSummary, LibraryDelta, LibrarySnapshot, LibrarySummary};

const MAX_DELTA_HISTORY: usize = 8;

#[derive(Debug, Clone)]
pub struct LocalLibraryState {
    pub revision: u64,
    pub digest: u64,
    pub games: HashMap<String, GameSummary>,
    pub recent_deltas: VecDeque<LibraryDelta>,
}

impl LocalLibraryState {
    pub fn empty() -> Self {
        let games = HashMap::new();
        let digest = compute_library_digest(&games);
        Self {
            revision: 0,
            digest,
            games,
            recent_deltas: VecDeque::new(),
        }
    }

    pub fn update_from_scan(
        &mut self,
        summaries: HashMap<String, GameSummary>,
        revision: u64,
    ) -> Option<LibraryDelta> {
        let new_digest = compute_library_digest(&summaries);
        let changed =
            self.revision != revision || self.digest != new_digest || self.games != summaries;

        if !changed {
            return None;
        }

        let delta = compute_library_delta(self.revision, revision, &self.games, &summaries);
        self.revision = revision;
        self.digest = new_digest;
        self.games = summaries;
        self.recent_deltas.push_back(delta.clone());
        while self.recent_deltas.len() > MAX_DELTA_HISTORY {
            self.recent_deltas.pop_front();
        }
        Some(delta)
    }

    pub fn delta_since(&self, from_rev: u64) -> Option<LibraryDelta> {
        self.recent_deltas
            .iter()
            .find(|delta| delta.from_rev == from_rev)
            .cloned()
    }
}

pub fn compute_library_digest(games: &HashMap<String, GameSummary>) -> u64 {
    let mut entries: Vec<&GameSummary> = games.values().collect();
    entries.sort_by(|a, b| a.id.cmp(&b.id));

    let mut hasher = std::collections::hash_map::DefaultHasher::new();
    for summary in entries {
        summary.id.hash(&mut hasher);
        summary.name.hash(&mut hasher);
        summary.size.hash(&mut hasher);
        summary.downloaded.hash(&mut hasher);
        summary.installed.hash(&mut hasher);
        summary.eti_version.hash(&mut hasher);
        summary.manifest_hash.hash(&mut hasher);
        summary.availability.hash(&mut hasher);
    }
    hasher.finish()
}

pub fn build_library_summary(state: &LocalLibraryState) -> LibrarySummary {
    LibrarySummary {
        library_rev: state.revision,
        library_digest: state.digest,
        game_count: state.games.len(),
    }
}

pub fn build_library_snapshot(state: &LocalLibraryState) -> LibrarySnapshot {
    let mut games: Vec<GameSummary> = state.games.values().cloned().collect();
    games.sort_by(|a, b| a.id.cmp(&b.id));
    LibrarySnapshot {
        library_rev: state.revision,
        games,
    }
}

pub fn compute_library_delta(
    from_rev: u64,
    to_rev: u64,
    previous: &HashMap<String, GameSummary>,
    next: &HashMap<String, GameSummary>,
) -> LibraryDelta {
    let mut added = Vec::new();
    let mut updated = Vec::new();
    let mut removed = Vec::new();

    for (game_id, summary) in next {
        match previous.get(game_id) {
            None => added.push(summary.clone()),
            Some(existing) => {
                if existing != summary {
                    updated.push(summary.clone());
                }
            }
        }
    }

    for game_id in previous.keys() {
        if !next.contains_key(game_id) {
            removed.push(game_id.clone());
        }
    }

    LibraryDelta {
        from_rev,
        to_rev,
        added,
        updated,
        removed,
    }
}
