use obnam::{
    blobs::{BlobRepository, BlobsError},
    chunk::{Label, Metadata},
    id::{Id, IdError},
};

#[derive(Debug)]
pub struct Repo {
    blobs: BlobRepository,
}

impl Repo {
    pub fn new(blobs: BlobRepository) -> Self {
        Self { blobs }
    }

    pub fn put(
        &mut self,
        id: impl Into<String>,
        label: impl Into<String>,
        chunk: impl Into<Vec<u8>>,
    ) -> Result<(), RepoError> {
        let id = id.into();
        let id = Id::try_from(id.as_str()).map_err(|err| RepoError::Id(id, err))?;
        let label = label.into();
        let label = Label::from(label.as_str());
        let chunk = chunk.into();
        self.blobs
            .add_blob(&chunk, &Metadata::from((&id, &label)))
            .map_err(|err| RepoError::Put(id, err))?;
        Ok(())
    }

    pub fn get(&self, id: impl AsRef<str>) -> Result<(String, Vec<u8>), RepoError> {
        let id = id.as_ref();
        let id = Id::try_from(id).map_err(|err| RepoError::Id(id.to_string(), err))?;
        let res = self.blobs.get_blob(&id);
        match res {
            Ok((blob, meta)) => Ok((meta.label().to_string(), blob)),
            Err(BlobsError::NoSuchBlob(id)) => Err(RepoError::NoSuchChunk(id)),
            Err(_) => panic!("unknown error {res:?}"),
        }
    }

    pub fn find(&self, wanted: impl AsRef<str>) -> Result<Vec<String>, RepoError> {
        let wanted = Label::from(wanted.as_ref());
        let metas = self
            .blobs
            .find_blobs(&wanted)
            .map_err(|err| RepoError::Find(wanted.clone(), err))?;
        Ok(metas
            .iter()
            .filter_map(|meta| {
                if meta.label() == &wanted {
                    Some(meta.id().to_string())
                } else {
                    None
                }
            })
            .collect())
    }

    pub fn delete(&mut self, id: impl AsRef<str>) -> Result<bool, RepoError> {
        let id = id.as_ref();
        let id = Id::try_from(id).map_err(|err| RepoError::Id(id.to_string(), err))?;
        if self.blobs.get_blob(&id).is_ok() {
            self.blobs
                .remove_blob(&id)
                .map_err(|err| RepoError::Delete(id, err))?;
            Ok(true)
        } else {
            Ok(false)
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum RepoError {
    #[error("failed to understand chunk ID {0:?}")]
    Id(String, #[source] IdError),

    #[error("no such chunk in repository: {0}")]
    NoSuchChunk(Id),

    #[error("failed to get chunk from repository: {0}")]
    Get(Id, #[source] BlobsError),

    #[error("failed to add chunk to repository: {0}")]
    Put(Id, #[source] BlobsError),

    #[error("failed to search for chunks in repository: {0}")]
    Find(Label, #[source] BlobsError),

    #[error("failed to remove chunk from repository: {0}")]
    Delete(Id, #[source] BlobsError),
}
