use bytes::Bytes;
use lanspread_db::db::{Game, GameFileDescription};
use serde::{Deserialize, Serialize};

pub const PROTOCOL_VERSION: u32 = 2;

pub use lanspread_db::db::Availability;

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct GameSummary {
    pub id: String,
    pub name: String,
    pub size: u64,
    pub downloaded: bool,
    pub installed: bool,
    pub eti_version: Option<String>,
    pub manifest_hash: u64,
    pub availability: Availability,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Hello {
    pub peer_id: String,
    pub proto_ver: u32,
    pub library_rev: u64,
    pub library_digest: u64,
    pub features: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HelloAck {
    pub peer_id: String,
    pub proto_ver: u32,
    pub library_rev: u64,
    pub library_digest: u64,
    pub features: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LibrarySummary {
    pub library_rev: u64,
    pub library_digest: u64,
    pub game_count: usize,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LibrarySnapshot {
    pub library_rev: u64,
    pub games: Vec<GameSummary>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LibraryDelta {
    pub from_rev: u64,
    pub to_rev: u64,
    pub added: Vec<GameSummary>,
    pub updated: Vec<GameSummary>,
    pub removed: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum Request {
    Ping,
    ListGames,
    GetGame {
        id: String,
    },
    GetGameFileData(GameFileDescription),
    GetGameFileChunk {
        game_id: String,
        relative_path: String,
        offset: u64,
        length: u64,
    },
    AnnounceGames(Vec<Game>),
    Hello(Hello),
    LibrarySummary(LibrarySummary),
    LibrarySnapshot(LibrarySnapshot),
    LibraryDelta(LibraryDelta),
    Goodbye {
        peer_id: String,
    },
    Invalid(Bytes, String),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Response {
    Pong,
    ListGames(Vec<Game>),
    GetGame {
        id: String,
        file_descriptions: Vec<GameFileDescription>,
    },
    HelloAck(HelloAck),
    GameNotFound(String),
    InvalidRequest(Bytes, String),
    EncodingError(String),
    DecodingError(Bytes, String),
    InternalPeerError(String),
}

// Add Message trait
pub trait Message {
    fn decode(bytes: Bytes) -> Self;
    fn encode(&self) -> Bytes;
}

// Implement for Request
impl Message for Request {
    fn decode(bytes: Bytes) -> Self {
        match serde_json::from_slice(&bytes) {
            Ok(t) => t,
            Err(e) => {
                tracing::error!(?e, "Request decoding error");
                Request::Invalid(bytes, e.to_string())
            }
        }
    }

    fn encode(&self) -> Bytes {
        match serde_json::to_vec(self) {
            Ok(s) => Bytes::from(s),
            Err(e) => {
                tracing::error!(?e, "Request encoding error");
                Bytes::from(format!(r#"{{"error": "encoding error: {e}"}}"#))
            }
        }
    }
}

// Implement for Response
impl Message for Response {
    fn decode(bytes: Bytes) -> Self {
        match serde_json::from_slice(&bytes) {
            Ok(t) => t,
            Err(e) => {
                tracing::error!(?e, "Response decoding error");
                Response::DecodingError(bytes, e.to_string())
            }
        }
    }

    fn encode(&self) -> Bytes {
        match serde_json::to_vec(self) {
            Ok(s) => Bytes::from(s),
            Err(e) => {
                tracing::error!(?e, "Response encoding error");
                Bytes::from(format!(r#"{{"error": "encoding error: {e}"}}"#))
            }
        }
    }
}
