use crate::error::{Result, SyncularError};
use serde_json::Value;
use std::ops::Deref;
use syncular_protocol::binary_snapshot as protocol_binary_snapshot;

pub use protocol_binary_snapshot::{
    BinarySnapshotCell, BinarySnapshotColumn, BinarySnapshotColumnType, BorrowedBinarySnapshotCell,
    DecodedBinarySnapshotRows, DecodedBinarySnapshotTable,
};

#[derive(Debug, Clone, PartialEq)]
pub struct BinarySnapshotPayload(protocol_binary_snapshot::BinarySnapshotPayload);

impl BinarySnapshotPayload {
    pub fn row_count(&self) -> usize {
        self.0.row_count()
    }

    pub fn bytes(&self) -> &[u8] {
        self.0.bytes()
    }

    pub fn row_cursor(&self) -> BinarySnapshotRowCursor<'_> {
        BinarySnapshotRowCursor {
            inner: self.0.row_cursor(),
        }
    }

    pub fn into_decoded_rows(self) -> Result<DecodedBinarySnapshotRows> {
        Ok(self.0.into_decoded_rows()?)
    }

    pub fn into_value_rows(self) -> Result<Vec<Value>> {
        Ok(self.0.into_value_rows()?)
    }
}

impl Deref for BinarySnapshotPayload {
    type Target = protocol_binary_snapshot::BinarySnapshotPayload;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<protocol_binary_snapshot::BinarySnapshotPayload> for BinarySnapshotPayload {
    fn from(payload: protocol_binary_snapshot::BinarySnapshotPayload) -> Self {
        Self(payload)
    }
}

pub trait BorrowedBinarySnapshotRawCellVisitor<'a> {
    fn visit_null(&mut self) -> Result<()>;
    fn visit_string_bytes(&mut self, value: &'a [u8]) -> Result<()>;
    fn visit_integer(&mut self, value: i64) -> Result<()>;
    fn visit_float(&mut self, value: f64) -> Result<()>;
    fn visit_boolean(&mut self, value: bool) -> Result<()>;
    fn visit_json_bytes(&mut self, value: &'a [u8]) -> Result<()>;
    fn visit_bytes(&mut self, value: &'a [u8]) -> Result<()>;
}

pub struct BinarySnapshotRowCursor<'a> {
    inner: protocol_binary_snapshot::BinarySnapshotRowCursor<'a>,
}

impl<'a> BinarySnapshotRowCursor<'a> {
    pub fn read_next_row<F>(&mut self, mut on_cell: F) -> Result<bool>
    where
        F: FnMut(usize, &BinarySnapshotColumn, BorrowedBinarySnapshotCell<'a>) -> Result<()>,
    {
        self.inner
            .read_next_row(|column_index, column, cell| on_cell(column_index, column, cell))
            .map_err(syncular_error_from_visit)
    }

    pub fn read_next_row_with_raw_visitor_trusted<V>(&mut self, visitor: &mut V) -> Result<bool>
    where
        V: BorrowedBinarySnapshotRawCellVisitor<'a>,
    {
        let mut adapter = RawCellVisitorAdapter { visitor };
        self.inner
            .read_next_row_with_raw_visitor_trusted(&mut adapter)
            .map_err(syncular_error_from_visit)
    }

    pub fn assert_done(&self) -> Result<()> {
        Ok(self.inner.assert_done()?)
    }
}

struct RawCellVisitorAdapter<'v, V> {
    visitor: &'v mut V,
}

impl<'a, V> protocol_binary_snapshot::BorrowedBinarySnapshotRawCellVisitor<'a>
    for RawCellVisitorAdapter<'_, V>
where
    V: BorrowedBinarySnapshotRawCellVisitor<'a>,
{
    type Error = SyncularError;

    fn visit_null(&mut self) -> std::result::Result<(), Self::Error> {
        self.visitor.visit_null()
    }

    fn visit_string_bytes(&mut self, value: &'a [u8]) -> std::result::Result<(), Self::Error> {
        self.visitor.visit_string_bytes(value)
    }

    fn visit_integer(&mut self, value: i64) -> std::result::Result<(), Self::Error> {
        self.visitor.visit_integer(value)
    }

    fn visit_float(&mut self, value: f64) -> std::result::Result<(), Self::Error> {
        self.visitor.visit_float(value)
    }

    fn visit_boolean(&mut self, value: bool) -> std::result::Result<(), Self::Error> {
        self.visitor.visit_boolean(value)
    }

    fn visit_json_bytes(&mut self, value: &'a [u8]) -> std::result::Result<(), Self::Error> {
        self.visitor.visit_json_bytes(value)
    }

    fn visit_bytes(&mut self, value: &'a [u8]) -> std::result::Result<(), Self::Error> {
        self.visitor.visit_bytes(value)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum SnapshotChunkRows {
    Json(Vec<Value>),
    Binary(DecodedBinarySnapshotRows),
    BinaryPayload(BinarySnapshotPayload),
}

impl SnapshotChunkRows {
    pub fn row_count(&self) -> usize {
        match self {
            Self::Json(rows) => rows.len(),
            Self::Binary(rows) => rows.row_count(),
            Self::BinaryPayload(rows) => rows.row_count(),
        }
    }

    pub fn is_empty(&self) -> bool {
        self.row_count() == 0
    }

    pub fn into_value_rows(self) -> Vec<Value> {
        self.try_into_value_rows()
            .expect("binary snapshot payload decodes into JSON rows")
    }

    pub fn try_into_value_rows(self) -> Result<Vec<Value>> {
        match self {
            Self::Json(rows) => Ok(rows),
            Self::Binary(rows) => Ok(rows.into_value_rows()),
            Self::BinaryPayload(rows) => rows.into_value_rows(),
        }
    }
}

pub fn decode_binary_snapshot_table(bytes: &[u8]) -> Result<DecodedBinarySnapshotTable> {
    Ok(protocol_binary_snapshot::decode_binary_snapshot_table(
        bytes,
    )?)
}

pub fn decode_binary_snapshot_rows(bytes: &[u8]) -> Result<DecodedBinarySnapshotRows> {
    Ok(protocol_binary_snapshot::decode_binary_snapshot_rows(
        bytes,
    )?)
}

pub fn decode_binary_snapshot_payload(bytes: Vec<u8>) -> Result<BinarySnapshotPayload> {
    Ok(protocol_binary_snapshot::decode_binary_snapshot_payload(bytes)?.into())
}

fn syncular_error_from_visit(
    error: protocol_binary_snapshot::BinarySnapshotVisitError<SyncularError>,
) -> SyncularError {
    match error {
        protocol_binary_snapshot::BinarySnapshotVisitError::Protocol(error) => error.into(),
        protocol_binary_snapshot::BinarySnapshotVisitError::Visitor(error) => error,
    }
}
