use std::path::{Path, PathBuf};
use anyhow::Result;
use redb_v1::ReadableTable;
use tempfile::NamedTempFile;
use tracing::info;
pub fn run(source: impl AsRef<Path>) -> Result<redb::Database> {
    let source = source.as_ref();
    let dir = source.parent().expect("database is not in root");
    let target = NamedTempFile::with_prefix_in("blobs.db.migrate", dir)?;
    let target = target.into_temp_path();
    info!("migrate {} to {}", source.display(), target.display());
    let old_db = redb_v1::Database::open(source)?;
    let new_db = redb::Database::create(&target)?;
    let rtx = old_db.begin_read()?;
    let wtx = new_db.begin_write()?;
    {
        let old_blobs = rtx.open_table(old::BLOBS_TABLE)?;
        let mut new_blobs = wtx.open_table(new::BLOBS_TABLE)?;
        let len = old_blobs.len()?;
        info!("migrate blobs table ({len} rows)");
        for (i, entry) in old_blobs.iter()?.enumerate() {
            let (key, value) = entry?;
            let key: crate::Hash = key.value().into();
            let value = value.value();
            if i > 0 && i % 1000 == 0 {
                info!("    row {i:>6} of {len}");
            }
            new_blobs.insert(key, value)?;
        }
        info!("migrate blobs table done");
        let old_tags = rtx.open_table(old::TAGS_TABLE)?;
        let mut new_tags = wtx.open_table(new::TAGS_TABLE)?;
        let len = old_tags.len()?;
        info!("migrate tags table ({len} rows)");
        for (i, entry) in old_tags.iter()?.enumerate() {
            let (key, value) = entry?;
            let key = key.value();
            let value: crate::HashAndFormat = value.value().into();
            if i > 0 && i % 1000 == 0 {
                info!("    row {i:>6} of {len}");
            }
            new_tags.insert(key, value)?;
        }
        info!("migrate tags table done");
        let old_inline_data = rtx.open_table(old::INLINE_DATA_TABLE)?;
        let mut new_inline_data = wtx.open_table(new::INLINE_DATA_TABLE)?;
        let len = old_inline_data.len()?;
        info!("migrate inline data table ({len} rows)");
        for (i, entry) in old_inline_data.iter()?.enumerate() {
            let (key, value) = entry?;
            let key: crate::Hash = key.value().into();
            let value = value.value();
            if i > 0 && i % 1000 == 0 {
                info!("    row {i:>6} of {len}");
            }
            new_inline_data.insert(key, value)?;
        }
        info!("migrate inline data table done");
        let old_inline_outboard = rtx.open_table(old::INLINE_OUTBOARD_TABLE)?;
        let mut new_inline_outboard = wtx.open_table(new::INLINE_OUTBOARD_TABLE)?;
        let len = old_inline_outboard.len()?;
        info!("migrate inline outboard table ({len} rows)");
        for (i, entry) in old_inline_outboard.iter()?.enumerate() {
            let (key, value) = entry?;
            let key: crate::Hash = key.value().into();
            let value = value.value();
            if i > 0 && i % 1000 == 0 {
                info!("    row {i:>6} of {len}");
            }
            new_inline_outboard.insert(key, value)?;
        }
        info!("migrate inline outboard table done");
    }
    wtx.commit()?;
    drop(rtx);
    drop(old_db);
    drop(new_db);
    let backup_path: PathBuf = {
        let mut p = source.to_owned().into_os_string();
        p.push(".backup-redb-v1");
        p.into()
    };
    info!("rename {} to {}", source.display(), backup_path.display());
    std::fs::rename(source, &backup_path)?;
    info!("rename {} to {}", target.display(), source.display());
    target.persist_noclobber(source)?;
    info!("opening migrated database from {}", source.display());
    let db = redb::Database::open(source)?;
    Ok(db)
}
mod new {
    pub(super) use super::super::tables::*;
}
mod old {
    use bytes::Bytes;
    use iroh_base::hash::BlobFormat;
    use postcard::experimental::max_size::MaxSize;
    use redb_v1::{RedbKey, RedbValue, TableDefinition, TypeName};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    use smallvec::SmallVec;
    use super::super::EntryState;
    use crate::util::Tag;
    pub const BLOBS_TABLE: TableDefinition<Hash, EntryState> = TableDefinition::new("blobs-0");
    pub const TAGS_TABLE: TableDefinition<Tag, HashAndFormat> = TableDefinition::new("tags-0");
    pub const INLINE_DATA_TABLE: TableDefinition<Hash, &[u8]> =
        TableDefinition::new("inline-data-0");
    pub const INLINE_OUTBOARD_TABLE: TableDefinition<Hash, &[u8]> =
        TableDefinition::new("inline-outboard-0");
    impl redb_v1::RedbValue for EntryState {
        type SelfType<'a> = EntryState;
        type AsBytes<'a> = SmallVec<[u8; 128]>;
        fn fixed_width() -> Option<usize> {
            None
        }
        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
        where
            Self: 'a,
        {
            postcard::from_bytes(data).unwrap()
        }
        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
        where
            Self: 'a,
            Self: 'b,
        {
            postcard::to_extend(value, SmallVec::new()).unwrap()
        }
        fn type_name() -> TypeName {
            TypeName::new("EntryState")
        }
    }
    impl RedbValue for HashAndFormat {
        type SelfType<'a> = Self;
        type AsBytes<'a> = [u8; Self::POSTCARD_MAX_SIZE];
        fn fixed_width() -> Option<usize> {
            Some(Self::POSTCARD_MAX_SIZE)
        }
        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
        where
            Self: 'a,
        {
            let t: &'a [u8; Self::POSTCARD_MAX_SIZE] = data.try_into().unwrap();
            postcard::from_bytes(t.as_slice()).unwrap()
        }
        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
        where
            Self: 'a,
            Self: 'b,
        {
            let mut res = [0u8; 33];
            postcard::to_slice(&value, &mut res).unwrap();
            res
        }
        fn type_name() -> TypeName {
            TypeName::new("iroh_base::HashAndFormat")
        }
    }
    impl RedbValue for Tag {
        type SelfType<'a> = Self;
        type AsBytes<'a> = bytes::Bytes;
        fn fixed_width() -> Option<usize> {
            None
        }
        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
        where
            Self: 'a,
        {
            Self(Bytes::copy_from_slice(data))
        }
        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
        where
            Self: 'a,
            Self: 'b,
        {
            value.0.clone()
        }
        fn type_name() -> TypeName {
            TypeName::new("Tag")
        }
    }
    impl RedbKey for Tag {
        fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
            data1.cmp(data2)
        }
    }
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    pub struct Hash([u8; 32]);
    impl Serialize for Hash {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            self.0.serialize(serializer)
        }
    }
    impl<'de> Deserialize<'de> for Hash {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            let data: [u8; 32] = Deserialize::deserialize(deserializer)?;
            Ok(Self(data))
        }
    }
    impl MaxSize for Hash {
        const POSTCARD_MAX_SIZE: usize = 32;
    }
    impl From<Hash> for crate::Hash {
        fn from(value: Hash) -> Self {
            value.0.into()
        }
    }
    impl RedbValue for Hash {
        type SelfType<'a> = Self;
        type AsBytes<'a> = &'a [u8; 32];
        fn fixed_width() -> Option<usize> {
            Some(32)
        }
        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
        where
            Self: 'a,
        {
            let contents: &'a [u8; 32] = data.try_into().unwrap();
            Hash(*contents)
        }
        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
        where
            Self: 'a,
            Self: 'b,
        {
            &value.0
        }
        fn type_name() -> TypeName {
            TypeName::new("iroh_base::Hash")
        }
    }
    impl RedbKey for Hash {
        fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
            data1.cmp(data2)
        }
    }
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, MaxSize)]
    pub struct HashAndFormat {
        pub hash: Hash,
        pub format: BlobFormat,
    }
    impl From<HashAndFormat> for crate::HashAndFormat {
        fn from(value: HashAndFormat) -> Self {
            crate::HashAndFormat {
                hash: value.hash.into(),
                format: value.format,
            }
        }
    }
    impl Serialize for HashAndFormat {
        fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            (self.hash, self.format).serialize(serializer)
        }
    }
    impl<'de> Deserialize<'de> for HashAndFormat {
        fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            let (hash, format) = <(Hash, BlobFormat)>::deserialize(deserializer)?;
            Ok(Self { hash, format })
        }
    }
}