use ella_common::TensorType;
use ella_engine::{
    registry::{TableId, TableRef},
    table::{
        info::{TableInfo, TopicInfo, ViewBuilder, ViewInfo},
        Column,
    },
    Plan,
};

use crate::gen::{self, table_info::Kind};

impl TryFrom<gen::TensorType> for TensorType {
    type Error = crate::Error;

    fn try_from(value: gen::TensorType) -> Result<Self, Self::Error> {
        Ok(match value {
            gen::TensorType::Unknown => todo!(),
            gen::TensorType::Bool => TensorType::Bool,
            gen::TensorType::Int8 => TensorType::Int8,
            gen::TensorType::Int16 => TensorType::Int16,
            gen::TensorType::Int32 => TensorType::Int32,
            gen::TensorType::Int64 => TensorType::Int64,
            gen::TensorType::Uint8 => TensorType::UInt8,
            gen::TensorType::Uint16 => TensorType::UInt16,
            gen::TensorType::Uint32 => TensorType::UInt32,
            gen::TensorType::Uint64 => TensorType::UInt64,
            gen::TensorType::Float32 => TensorType::Float32,
            gen::TensorType::Float64 => TensorType::Float64,
            gen::TensorType::Timestamp => TensorType::Timestamp,
            gen::TensorType::Duration => TensorType::Duration,
            gen::TensorType::String => TensorType::String,
        })
    }
}

impl From<TensorType> for gen::TensorType {
    fn from(value: TensorType) -> Self {
        match value {
            TensorType::Bool => gen::TensorType::Bool,
            TensorType::Int8 => gen::TensorType::Int8,
            TensorType::Int16 => gen::TensorType::Int16,
            TensorType::Int32 => gen::TensorType::Int32,
            TensorType::Int64 => gen::TensorType::Int64,
            TensorType::UInt8 => gen::TensorType::Uint8,
            TensorType::UInt16 => gen::TensorType::Uint16,
            TensorType::UInt32 => gen::TensorType::Uint32,
            TensorType::UInt64 => gen::TensorType::Uint64,
            TensorType::Float32 => gen::TensorType::Float32,
            TensorType::Float64 => gen::TensorType::Float64,
            TensorType::Timestamp => gen::TensorType::Timestamp,
            TensorType::Duration => gen::TensorType::Duration,
            TensorType::String => gen::TensorType::String,
        }
    }
}

impl From<gen::TableId> for TableId<'static> {
    fn from(value: gen::TableId) -> Self {
        TableId {
            catalog: value.catalog.into(),
            schema: value.schema.into(),
            table: value.table.into(),
        }
    }
}

impl From<gen::TableRef> for TableRef<'static> {
    fn from(value: gen::TableRef) -> Self {
        TableRef {
            catalog: value.catalog.map(Into::into),
            schema: value.schema.map(Into::into),
            table: value.table.into(),
        }
    }
}

impl<'a> From<TableRef<'a>> for gen::TableRef {
    fn from(value: TableRef<'a>) -> Self {
        Self {
            catalog: value.catalog.map(|c| c.to_string()),
            schema: value.schema.map(|s| s.to_string()),
            table: value.table.to_string(),
        }
    }
}

impl<'a> From<TableId<'a>> for gen::TableId {
    fn from(value: TableId<'a>) -> Self {
        Self {
            catalog: value.catalog.to_string(),
            schema: value.schema.to_string(),
            table: value.table.to_string(),
        }
    }
}

impl TryFrom<gen::Column> for Column {
    type Error = crate::Error;

    fn try_from(value: gen::Column) -> Result<Self, Self::Error> {
        let mut builder = Column::builder(&value.name, value.data_type().try_into()?);
        if !value.row_shape.is_empty() {
            builder = builder.row_shape(
                value
                    .row_shape
                    .into_iter()
                    .map(|x| x as usize)
                    .collect::<Vec<_>>(),
            );
        }
        if value.required {
            builder = builder.required();
        }

        Ok(builder.build())
    }
}

impl From<Column> for gen::Column {
    fn from(value: Column) -> Self {
        Self {
            name: value.name,
            data_type: gen::TensorType::from(value.data_type).into(),
            row_shape: value
                .row_shape
                .map_or_else(Vec::new, |row| row.into_iter().map(|r| r as u32).collect()),
            required: value.required,
        }
    }
}

impl TryFrom<gen::TopicInfo> for TopicInfo {
    type Error = crate::Error;

    fn try_from(value: gen::TopicInfo) -> Result<Self, Self::Error> {
        let mut builder = Self::builder().append_time(false);
        for col in value.columns {
            builder = builder.column(Column::try_from(col)?);
        }

        for index in value.index {
            builder = builder.index(index.column, index.ascending);
        }

        if value.temporary {
            builder = builder.temporary();
        }
        if let Some(config) = value.config.as_deref() {
            builder = builder.config(serde_json::from_slice(config)?);
        }

        Ok(builder.build())
    }
}

impl TryFrom<TopicInfo> for gen::TopicInfo {
    type Error = crate::Error;

    fn try_from(value: TopicInfo) -> Result<Self, Self::Error> {
        let columns = value
            .columns()
            .iter()
            .map(|c| c.clone().into())
            .collect::<Vec<_>>();
        let index = value
            .index()
            .iter()
            .map(|i| gen::TableIndex {
                column: i.column.clone(),
                ascending: i.ascending,
            })
            .collect::<Vec<_>>();
        let config = if let Some(config) = value.config() {
            Some(serde_json::to_vec(config)?)
        } else {
            None
        };

        Ok(Self {
            columns,
            temporary: value.temporary(),
            index,
            config,
        })
    }
}

impl TryFrom<gen::ViewInfo> for ViewInfo {
    type Error = crate::Error;

    fn try_from(value: gen::ViewInfo) -> Result<Self, Self::Error> {
        let plan = Plan::from_bytes(&value.plan)?;
        let mut builder = ViewBuilder::new(plan);

        if let Some(definition) = value.definition {
            builder = builder.definition(definition);
        }
        if value.materialized {
            builder = builder.materialize();
        }
        for index in value.index {
            builder = builder.index(index.column, index.ascending)?;
        }
        if let Some(config) = value.config.as_deref() {
            builder = builder.config(serde_json::from_slice(config)?);
        }
        Ok(builder.build())
    }
}

impl TryFrom<ViewInfo> for gen::ViewInfo {
    type Error = crate::Error;

    fn try_from(value: ViewInfo) -> Result<Self, Self::Error> {
        let config = if let Some(config) = value.config() {
            Some(serde_json::to_vec(config)?)
        } else {
            None
        };
        let index = value.index().map_or_else(Vec::new, |index| {
            index
                .iter()
                .map(|i| gen::TableIndex {
                    column: i.column.clone(),
                    ascending: i.ascending,
                })
                .collect()
        });
        Ok(Self {
            plan: value.plan().to_bytes(),
            definition: value.definition().map(|def| def.to_string()),
            materialized: value.materialized(),
            index,
            config,
        })
    }
}

impl TryFrom<gen::TableInfo> for TableInfo {
    type Error = crate::Error;

    fn try_from(value: gen::TableInfo) -> Result<Self, Self::Error> {
        match value.kind {
            Some(Kind::Topic(topic)) => Ok(TopicInfo::try_from(topic)?.into()),
            Some(Kind::View(view)) => Ok(ViewInfo::try_from(view)?.into()),
            None => todo!(),
        }
    }
}

impl TryFrom<TableInfo> for gen::TableInfo {
    type Error = crate::Error;

    fn try_from(value: TableInfo) -> Result<Self, Self::Error> {
        Ok(match value {
            TableInfo::Topic(topic) => gen::TableInfo {
                kind: Some(Kind::Topic(topic.try_into()?)),
            },
            TableInfo::View(view) => gen::TableInfo {
                kind: Some(Kind::View(view.try_into()?)),
            },
        })
    }
}
