use futures::future::BoxFuture;
use futures::future::FutureExt;
use tokio::io::AsyncRead;
use tokio::io::AsyncReadExt;

use crate::decode::extract_columns;
use crate::decode::extract_symbol_names;
use crate::error::CoreError;
use crate::error::CoreResult;
use crate::frame::Encoding;
use crate::protocol::Attribute;
use crate::protocol::Primitive;
use crate::protocol::TypeCode;
use crate::value::Atom;
use crate::value::Dictionary;
use crate::value::List;
use crate::value::Table;
use crate::value::Value;
use crate::value::Vector;
use crate::value::VectorData;

/// Asynchronous reader for q value components.
///
/// Wraps an `AsyncRead` source and provides async methods to read
/// primitive types and byte chunks, allowing the decoder to wait
/// for data without blocking.
///
/// Only little-endian payloads are supported (matching the rest of qroissant).
pub struct PipelinedReader<R> {
    reader: R,
}

impl<R: AsyncRead + Unpin> PipelinedReader<R> {
    /// Creates a new pipelined reader.
    ///
    /// Returns `UnsupportedEndianness` for big-endian payloads, matching
    /// the behaviour of `decode_value()` and `decode_message()`.
    pub fn new(reader: R, encoding: Encoding) -> CoreResult<Self> {
        if encoding != Encoding::LittleEndian {
            return Err(CoreError::UnsupportedEndianness(encoding));
        }
        Ok(Self { reader })
    }

    pub async fn read_u8(&mut self) -> CoreResult<u8> {
        let mut buf = [0_u8; 1];
        self.reader.read_exact(&mut buf).await?;
        Ok(buf[0])
    }

    pub async fn read_i8(&mut self) -> CoreResult<i8> {
        Ok(self.read_u8().await? as i8)
    }

    pub async fn read_i16(&mut self) -> CoreResult<i16> {
        let mut buf = [0_u8; 2];
        self.reader.read_exact(&mut buf).await?;
        Ok(i16::from_le_bytes(buf))
    }

    pub async fn read_i32(&mut self) -> CoreResult<i32> {
        let mut buf = [0_u8; 4];
        self.reader.read_exact(&mut buf).await?;
        Ok(i32::from_le_bytes(buf))
    }

    pub async fn read_i64(&mut self) -> CoreResult<i64> {
        let mut buf = [0_u8; 8];
        self.reader.read_exact(&mut buf).await?;
        Ok(i64::from_le_bytes(buf))
    }

    pub async fn read_f32(&mut self) -> CoreResult<f32> {
        let mut buf = [0_u8; 4];
        self.reader.read_exact(&mut buf).await?;
        Ok(f32::from_le_bytes(buf))
    }

    pub async fn read_f64(&mut self) -> CoreResult<f64> {
        let mut buf = [0_u8; 8];
        self.reader.read_exact(&mut buf).await?;
        Ok(f64::from_le_bytes(buf))
    }

    pub async fn read_guid(&mut self) -> CoreResult<[u8; 16]> {
        let mut buf = [0_u8; 16];
        self.reader.read_exact(&mut buf).await?;
        Ok(buf)
    }

    pub async fn read_length(&mut self) -> CoreResult<usize> {
        let length = self.read_i32().await?;
        usize::try_from(length).map_err(|_| CoreError::InvalidCollectionLength(length))
    }

    pub async fn read_bytes(&mut self, len: usize) -> CoreResult<bytes::Bytes> {
        let mut buf = vec![0_u8; len];
        self.reader.read_exact(&mut buf).await?;
        Ok(bytes::Bytes::from(buf))
    }

    /// Reads a null-terminated symbol.
    ///
    /// Reads one byte at a time until a null terminator is found.
    /// In practice the underlying reader is buffered (e.g. `BufReader`
    /// or `DecompressingReader` with an 8 KB buffer), so single-byte
    /// `read_exact` calls are cheap — they copy from the user-space buffer
    /// without issuing a syscall.
    pub async fn read_symbol(&mut self) -> CoreResult<bytes::Bytes> {
        let mut buf = Vec::new();
        loop {
            let b = self.read_u8().await?;
            if b == 0 {
                return Ok(bytes::Bytes::from(buf));
            }
            buf.push(b);
        }
    }

    pub async fn read_vec<T: bytemuck::Pod + bytemuck::AnyBitPattern>(
        &mut self,
        count: usize,
    ) -> CoreResult<Vec<T>> {
        let _byte_len = count
            .checked_mul(std::mem::size_of::<T>())
            .ok_or(CoreError::LengthOverflow(count))?;
        let mut values = vec![T::zeroed(); count];
        let dst: &mut [u8] = bytemuck::cast_slice_mut(&mut values);
        self.reader.read_exact(dst).await?;
        Ok(values)
    }

    /// Reads `count` elements of `T` into a T-aligned allocation and exposes
    /// the result as a `bytes::Bytes`. Required for downstream `cast_slice`
    /// callers (Arrow projection, `VectorData::as_*_slice`) that need the
    /// payload aligned to `align_of::<T>()`. A raw `Vec<u8>` would only
    /// guarantee align=1 and can panic on `cast_slice::<u8, T>`.
    pub async fn read_typed_bytes<T>(&mut self, count: usize) -> CoreResult<bytes::Bytes>
    where
        T: bytemuck::Pod + bytemuck::AnyBitPattern + Send + Sync + 'static,
    {
        let values: Vec<T> = self.read_vec::<T>(count).await?;
        Ok(bytes::Bytes::from_owner(AlignedTBuf(values)))
    }
}

struct AlignedTBuf<T: bytemuck::Pod>(Vec<T>);

impl<T: bytemuck::Pod> AsRef<[u8]> for AlignedTBuf<T> {
    fn as_ref(&self) -> &[u8] {
        bytemuck::cast_slice(&self.0)
    }
}

pub async fn decode_value_async<R: AsyncRead + Unpin + Send>(
    reader: &mut PipelinedReader<R>,
) -> CoreResult<Value> {
    decode_inner_async(reader).await
}

fn decode_inner_async<'a, R: AsyncRead + Unpin + Send>(
    reader: &'a mut PipelinedReader<R>,
) -> BoxFuture<'a, CoreResult<Value>> {
    async move {
        let type_code_byte = reader.read_i8().await?;
        let type_code = TypeCode::try_from(type_code_byte)?;
        match type_code.shape() {
            crate::protocol::Shape::Atom => {
                let primitive = type_code
                    .primitive()
                    .ok_or(CoreError::InvalidTypeCode(type_code.into()))?;
                Ok(Value::Atom(decode_atom_async(reader, primitive).await?))
            }
            crate::protocol::Shape::Vector => {
                let primitive = type_code
                    .primitive()
                    .ok_or(CoreError::InvalidTypeCode(type_code.into()))?;
                let attribute = Attribute::try_from(reader.read_i8().await?)?;
                let length = reader.read_length().await?;
                Ok(Value::Vector(
                    decode_vector_async(reader, primitive, attribute, length).await?,
                ))
            }
            crate::protocol::Shape::List => {
                let attribute = Attribute::try_from(reader.read_i8().await?)?;
                let length = reader.read_length().await?;
                let mut values = Vec::with_capacity(length);
                for _ in 0..length {
                    values.push(decode_inner_async(reader).await?);
                }
                Ok(Value::List(List::new(attribute, values)))
            }
            crate::protocol::Shape::Dictionary => {
                let sorted = type_code == TypeCode::SortedDictionary;
                let keys = decode_inner_async(reader).await?;
                let values = decode_inner_async(reader).await?;
                let dict = Dictionary::new(sorted, keys, values);
                dict.validate()?;
                Ok(Value::Dictionary(dict))
            }
            crate::protocol::Shape::Table => {
                let attribute = Attribute::try_from(reader.read_i8().await?)?;
                let dict_value = decode_inner_async(reader).await?;
                match dict_value {
                    Value::Dictionary(dict) => {
                        let names = extract_symbol_names(dict.keys())?;
                        let columns = extract_columns(dict.values())?;
                        let table = Table::new(attribute, names, columns);
                        table.validate()?;
                        Ok(Value::Table(table))
                    }
                    _ => Err(CoreError::InvalidStructure(
                        "q table payload must contain a dictionary body".to_string(),
                    )),
                }
            }
            crate::protocol::Shape::UnaryPrimitive => Ok(Value::UnaryPrimitive {
                opcode: reader.read_i8().await?,
            }),
            crate::protocol::Shape::Error => {
                let error_msg = reader.read_symbol().await?;
                Err(CoreError::QRuntime(
                    String::from_utf8_lossy(&error_msg).into(),
                ))
            }
        }
    }
    .boxed()
}

async fn decode_atom_async<R: AsyncRead + Unpin + Send>(
    reader: &mut PipelinedReader<R>,
    primitive: Primitive,
) -> CoreResult<Atom> {
    Ok(match primitive {
        Primitive::Boolean => Atom::Boolean(reader.read_u8().await? != 0),
        Primitive::Guid => Atom::Guid(reader.read_guid().await?),
        Primitive::Byte => Atom::Byte(reader.read_u8().await?),
        Primitive::Short => Atom::Short(reader.read_i16().await?),
        Primitive::Int => Atom::Int(reader.read_i32().await?),
        Primitive::Long => Atom::Long(reader.read_i64().await?),
        Primitive::Real => Atom::Real(reader.read_f32().await?),
        Primitive::Float => Atom::Float(reader.read_f64().await?),
        Primitive::Char => Atom::Char(reader.read_u8().await?),
        Primitive::Symbol => Atom::Symbol(reader.read_symbol().await?),
        Primitive::Timestamp => Atom::Timestamp(reader.read_i64().await?),
        Primitive::Month => Atom::Month(reader.read_i32().await?),
        Primitive::Date => Atom::Date(reader.read_i32().await?),
        Primitive::Datetime => Atom::Datetime(reader.read_f64().await?),
        Primitive::Timespan => Atom::Timespan(reader.read_i64().await?),
        Primitive::Minute => Atom::Minute(reader.read_i32().await?),
        Primitive::Second => Atom::Second(reader.read_i32().await?),
        Primitive::Time => Atom::Time(reader.read_i32().await?),
        Primitive::Mixed => unreachable!("mixed values are not encoded as atoms"),
    })
}

async fn decode_vector_async<R: AsyncRead + Unpin + Send>(
    reader: &mut PipelinedReader<R>,
    primitive: Primitive,
    attribute: Attribute,
    length: usize,
) -> CoreResult<Vector> {
    let data = match primitive {
        Primitive::Boolean => VectorData::Boolean(reader.read_bytes(length).await?),
        Primitive::Guid => {
            let byte_len = length
                .checked_mul(16)
                .ok_or(CoreError::LengthOverflow(length))?;
            VectorData::Guid(reader.read_bytes(byte_len).await?)
        }
        Primitive::Byte => VectorData::Byte(reader.read_bytes(length).await?),
        Primitive::Short => VectorData::Short(reader.read_typed_bytes::<i16>(length).await?),
        Primitive::Int => VectorData::Int(reader.read_typed_bytes::<i32>(length).await?),
        Primitive::Long => VectorData::Long(reader.read_typed_bytes::<i64>(length).await?),
        Primitive::Real => VectorData::Real(reader.read_typed_bytes::<f32>(length).await?),
        Primitive::Float => VectorData::Float(reader.read_typed_bytes::<f64>(length).await?),
        Primitive::Char => VectorData::Char(reader.read_bytes(length).await?),
        Primitive::Symbol => {
            let mut values = Vec::with_capacity(length);
            for _ in 0..length {
                values.push(reader.read_symbol().await?);
            }
            VectorData::Symbol(values)
        }
        Primitive::Timestamp => VectorData::Timestamp(reader.read_typed_bytes::<i64>(length).await?),
        Primitive::Month => VectorData::Month(reader.read_typed_bytes::<i32>(length).await?),
        Primitive::Date => VectorData::Date(reader.read_typed_bytes::<i32>(length).await?),
        Primitive::Datetime => VectorData::Datetime(reader.read_typed_bytes::<f64>(length).await?),
        Primitive::Timespan => VectorData::Timespan(reader.read_typed_bytes::<i64>(length).await?),
        Primitive::Minute => VectorData::Minute(reader.read_typed_bytes::<i32>(length).await?),
        Primitive::Second => VectorData::Second(reader.read_typed_bytes::<i32>(length).await?),
        Primitive::Time => VectorData::Time(reader.read_typed_bytes::<i32>(length).await?),
        Primitive::Mixed => unreachable!("mixed values are not encoded as vectors"),
    };

    Ok(Vector::new(attribute, data))
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use super::*;

    #[tokio::test]
    async fn test_decode_atom_async() -> CoreResult<()> {
        let mut data = Vec::new();
        data.push(TypeCode::IntAtom as u8);
        data.extend_from_slice(&42_i32.to_le_bytes());

        let mut reader = PipelinedReader::new(Cursor::new(data), Encoding::LittleEndian).unwrap();
        let value = decode_value_async(&mut reader).await?;

        assert_eq!(value, Value::Atom(Atom::Int(42)));
        Ok(())
    }

    #[tokio::test]
    async fn test_decode_vector_async() -> CoreResult<()> {
        let mut data = Vec::new();
        data.push(TypeCode::IntVector as u8);
        data.push(0_u8); // attribute None
        data.extend_from_slice(&2_i32.to_le_bytes()); // length 2
        data.extend_from_slice(&10_i32.to_le_bytes());
        data.extend_from_slice(&20_i32.to_le_bytes());

        let mut reader = PipelinedReader::new(Cursor::new(data), Encoding::LittleEndian).unwrap();
        let value = decode_value_async(&mut reader).await?;

        match &value {
            Value::Vector(vector) => {
                assert_eq!(vector.data().as_i32_slice(), &[10, 20]);
            }
            _ => panic!("Expected Vector, got {:?}", value),
        }
        Ok(())
    }

    #[tokio::test]
    async fn test_decode_table_async() -> CoreResult<()> {
        let mut data = Vec::new();
        data.push(TypeCode::Table as u8);
        data.push(0_u8); // attribute None

        // Dictionary prefix
        data.push(TypeCode::Dictionary as u8);

        // Dictionary (keys)
        data.push(TypeCode::SymbolVector as u8);
        data.push(0_u8); // attribute None
        data.extend_from_slice(&1_i32.to_le_bytes()); // 1 column name
        data.extend_from_slice(b"col1\0");

        // Dictionary (values)
        data.push(TypeCode::GeneralList as u8);
        data.push(0_u8); // attribute None
        data.extend_from_slice(&1_i32.to_le_bytes()); // 1 column

        // Column 1: Int Vector [100, 200]
        data.push(TypeCode::IntVector as u8);
        data.push(0_u8);
        data.extend_from_slice(&2_i32.to_le_bytes());
        data.extend_from_slice(&100_i32.to_le_bytes());
        data.extend_from_slice(&200_i32.to_le_bytes());

        let mut reader = PipelinedReader::new(Cursor::new(data), Encoding::LittleEndian).unwrap();
        let value = decode_value_async(&mut reader).await?;

        match &value {
            Value::Table(table) => {
                assert_eq!(table.num_columns(), 1);
                assert_eq!(&table.column_names()[0][..], b"col1");
                match &table.columns()[0] {
                    Value::Vector(v) => {
                        assert_eq!(v.data().as_i32_slice(), &[100, 200]);
                    }
                    _ => panic!("Expected Vector"),
                }
            }
            _ => panic!("Expected Table, got {:?}", value),
        }
        Ok(())
    }

    #[tokio::test]
    async fn test_rejects_big_endian() {
        let result = PipelinedReader::new(Cursor::new(vec![]), Encoding::BigEndian);
        assert!(matches!(
            result,
            Err(CoreError::UnsupportedEndianness(Encoding::BigEndian))
        ));
    }

    /// Regression: KDB IPC packs columns with no inter-column padding. A
    /// symbol col of odd byte length leaves the following numeric column
    /// at a misaligned wire offset. Async pipelined decode used to back
    /// typed columns with a plain `Vec<u8>` (align=1), which let arrow
    /// `cast_slice::<u8, i64>` panic on consumers / allocators that don't
    /// happen to over-align byte allocations.
    #[tokio::test]
    async fn test_decode_misaligned_table_async() -> CoreResult<()> {
        let mut data = Vec::new();
        data.push(TypeCode::Table as u8);
        data.push(0_u8);

        data.push(TypeCode::Dictionary as u8);

        data.push(TypeCode::SymbolVector as u8);
        data.push(0_u8);
        data.extend_from_slice(&2_i32.to_le_bytes());
        data.extend_from_slice(b"sym\0");
        data.extend_from_slice(b"val\0");

        data.push(TypeCode::GeneralList as u8);
        data.push(0_u8);
        data.extend_from_slice(&2_i32.to_le_bytes());

        // Column 1: Symbol vector of length 2 with odd total payload to
        // throw downstream Long column off 8-byte alignment.
        data.push(TypeCode::SymbolVector as u8);
        data.push(0_u8);
        data.extend_from_slice(&2_i32.to_le_bytes());
        data.extend_from_slice(b"ab\0");
        data.extend_from_slice(b"cdef\0");

        // Column 2: Long vector [123, 456] — wire-offset now misaligned.
        data.push(TypeCode::LongVector as u8);
        data.push(0_u8);
        data.extend_from_slice(&2_i32.to_le_bytes());
        data.extend_from_slice(&123_i64.to_le_bytes());
        data.extend_from_slice(&456_i64.to_le_bytes());

        let mut reader = PipelinedReader::new(Cursor::new(data), Encoding::LittleEndian).unwrap();
        let value = decode_value_async(&mut reader).await?;

        match &value {
            Value::Table(table) => {
                assert_eq!(table.num_columns(), 2);
                match &table.columns()[1] {
                    Value::Vector(v) => {
                        // This call previously panicked under cast_slice on
                        // misaligned backing storage.
                        assert_eq!(v.data().as_i64_slice(), &[123, 456]);
                    }
                    _ => panic!("Expected Long Vector"),
                }
            }
            _ => panic!("Expected Table, got {:?}", value),
        }
        Ok(())
    }

    #[tokio::test]
    async fn test_negative_length_gives_proper_error() -> CoreResult<()> {
        let mut data = Vec::new();
        data.push(TypeCode::IntVector as u8);
        data.push(0_u8); // attribute None
        data.extend_from_slice(&(-1_i32).to_le_bytes()); // negative length

        let mut reader = PipelinedReader::new(Cursor::new(data), Encoding::LittleEndian).unwrap();
        let err = decode_value_async(&mut reader).await.unwrap_err();
        assert!(
            matches!(err, CoreError::InvalidCollectionLength(-1)),
            "expected InvalidCollectionLength(-1), got {:?}",
            err
        );
        Ok(())
    }
}
