// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! SST block infrastructure compatible with Java Paimon's sst package.
//!
//! Block layout:
//! ```text
//!     +---------------+
//!     | Block Trailer |  (5 bytes: 1 byte compression type + 4 bytes CRC32)
//!     +---------------+
//!     |  Block Data   |
//!     +---------------+--------------------------------+----+
//!     | key len | key bytes | value len | value bytes  |    |
//!     +------------------------------------------------+    |
//!     | key len | key bytes | value len | value bytes  |    +-> Key-Value pairs
//!     +------------------------------------------------+    |
//!     |                  ... ...                       |    |
//!     +------------------------------------------------+----+
//!     | entry pos | entry pos |     ...    | entry pos |    +-> optional, for unaligned block
//!     +------------------------------------------------+----+
//!     |   entry num  /  entry size   |   aligned type  |
//!     +------------------------------------------------+
//! ```

use crate::btree::var_len::{
    decode_var_int, decode_var_int_from_slice, encode_var_int, encode_var_int_to_slice,
};
use std::cmp::Ordering;
use std::io::{self, Cursor, Read, Write};

/// Block compression type, compatible with Java's BlockCompressionType.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BlockCompressionType {
    None = 0,
    Zstd = 1,
    Lz4 = 2,
    Lzo = 3,
}

impl BlockCompressionType {
    pub fn from_persistent_id(id: u8) -> io::Result<Self> {
        match id {
            0 => Ok(Self::None),
            1 => Ok(Self::Zstd),
            2 => Ok(Self::Lz4),
            3 => Ok(Self::Lzo),
            _ => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Unknown compression type: {id}"),
            )),
        }
    }
}

/// Block aligned type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BlockAlignedType {
    Aligned = 0,
    Unaligned = 1,
}

impl BlockAlignedType {
    pub fn from_byte(b: u8) -> io::Result<Self> {
        match b {
            0 => Ok(Self::Aligned),
            1 => Ok(Self::Unaligned),
            _ => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Unknown aligned type: {b}"),
            )),
        }
    }
}

/// Block trailer: compression type (1 byte) + CRC32 (4 bytes) = 5 bytes.
pub const BLOCK_TRAILER_LENGTH: usize = 5;

pub struct BlockTrailer {
    pub compression_type: BlockCompressionType,
    pub crc32c: u32,
}

impl BlockTrailer {
    #[allow(dead_code)]
    pub fn write_to(&self, out: &mut impl Write) -> io::Result<()> {
        out.write_all(&[self.compression_type as u8])?;
        out.write_all(&(self.crc32c as i32).to_le_bytes())?;
        Ok(())
    }

    /// Encode trailer to a fixed-size stack array.
    pub fn to_bytes(&self) -> [u8; BLOCK_TRAILER_LENGTH] {
        let crc_bytes = (self.crc32c as i32).to_le_bytes();
        [
            self.compression_type as u8,
            crc_bytes[0],
            crc_bytes[1],
            crc_bytes[2],
            crc_bytes[3],
        ]
    }

    pub fn read_from(data: &[u8]) -> io::Result<Self> {
        if data.len() < BLOCK_TRAILER_LENGTH {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Block trailer too short",
            ));
        }
        let compression_type = BlockCompressionType::from_persistent_id(data[0])?;
        let crc32c = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
        Ok(Self {
            compression_type,
            crc32c,
        })
    }
}

/// Compute CRC32 of block data + compression type byte (compatible with Java).
pub fn compute_crc32(data: &[u8], compression_type: BlockCompressionType) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(data);
    hasher.update(&[compression_type as u8]);
    hasher.finalize()
}

/// BlockHandle: offset + size of a block in the file.
pub const BLOCK_HANDLE_MAX_ENCODED_LENGTH: usize = 14; // 9 (var long) + 5 (var int)

#[derive(Debug, Clone, Copy, Default)]
pub struct BlockHandle {
    pub offset: u64,
    pub size: u32,
}

impl BlockHandle {
    pub fn new(offset: u64, size: u32) -> Self {
        Self { offset, size }
    }

    pub fn full_block_size(&self) -> u32 {
        self.size + BLOCK_TRAILER_LENGTH as u32
    }

    pub fn write_to(&self, out: &mut impl Write) -> io::Result<()> {
        crate::btree::var_len::encode_var_long(out, self.offset as i64)?;
        encode_var_int(out, self.size as i32)?;
        Ok(())
    }

    pub fn read_from(input: &mut impl Read) -> io::Result<Self> {
        let offset = crate::btree::var_len::decode_var_long(input)? as u64;
        let size = decode_var_int(input)? as u32;
        Ok(Self { offset, size })
    }

    pub fn encode(&self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(BLOCK_HANDLE_MAX_ENCODED_LENGTH);
        self.write_to(&mut buf).unwrap();
        buf
    }

    /// Encode into a stack buffer, returning (buffer, length).
    pub fn encode_to_buf(&self) -> ([u8; BLOCK_HANDLE_MAX_ENCODED_LENGTH], usize) {
        let mut buf = [0u8; BLOCK_HANDLE_MAX_ENCODED_LENGTH];
        let mut cursor = io::Cursor::new(&mut buf[..]);
        self.write_to(&mut cursor).unwrap();
        let len = cursor.position() as usize;
        (buf, len)
    }

    pub fn decode(data: &[u8]) -> io::Result<Self> {
        Self::read_from(&mut Cursor::new(data))
    }
}

/// BlockWriter: builds a block of key-value pairs.
pub struct BlockWriter {
    data: Vec<u8>,
    positions: Vec<i32>,
    aligned_size: i32,
    aligned: bool,
}

impl BlockWriter {
    pub fn new(capacity: usize) -> Self {
        Self {
            data: Vec::with_capacity(capacity),
            positions: Vec::with_capacity(32),
            aligned_size: 0,
            aligned: true,
        }
    }

    pub fn add(&mut self, key: &[u8], value: &[u8]) {
        let start = self.data.len() as i32;

        // key_len (var int) + key + value_len (var int) + value
        let mut var_buf = [0u8; 5];
        let n = encode_var_int_to_slice(&mut var_buf, 0, key.len() as i32);
        self.data.extend_from_slice(&var_buf[..n]);
        self.data.extend_from_slice(key);

        let n = encode_var_int_to_slice(&mut var_buf, 0, value.len() as i32);
        self.data.extend_from_slice(&var_buf[..n]);
        self.data.extend_from_slice(value);

        let end = self.data.len() as i32;
        self.positions.push(start);

        if self.aligned {
            let current_size = end - start;
            if self.aligned_size == 0 {
                self.aligned_size = current_size;
            } else {
                self.aligned = self.aligned_size == current_size;
            }
        }
    }

    pub fn entry_count(&self) -> usize {
        self.positions.len()
    }

    /// Estimated memory usage of the block.
    pub fn memory(&self) -> usize {
        let mut mem = self.data.len() + 5;
        if !self.aligned {
            mem += self.positions.len() * 4;
        }
        mem
    }

    /// Finish building the block, returning the raw block bytes.
    pub fn finish(&mut self) -> Vec<u8> {
        if self.positions.is_empty() {
            self.aligned = false;
        }

        if self.aligned {
            self.data
                .extend_from_slice(&(self.aligned_size).to_le_bytes());
        } else {
            for &pos in &self.positions {
                self.data.extend_from_slice(&pos.to_le_bytes());
            }
            self.data
                .extend_from_slice(&(self.positions.len() as i32).to_le_bytes());
        }
        self.data.push(if self.aligned {
            BlockAlignedType::Aligned as u8
        } else {
            BlockAlignedType::Unaligned as u8
        });

        let result = std::mem::take(&mut self.data);
        self.reset();
        result
    }

    pub fn reset(&mut self) {
        self.data.clear();
        self.positions.clear();
        self.aligned_size = 0;
        self.aligned = true;
    }
}

/// BlockReader: reads a block and supports binary search.
pub struct BlockReader {
    /// The raw data portion (key-value pairs only, no index/footer).
    pub(crate) data: Vec<u8>,
    record_count: usize,
    /// For aligned blocks: record_size; for unaligned: index offsets.
    seek_info: SeekInfo,
}

enum SeekInfo {
    Aligned { record_size: usize },
    Unaligned { offsets: Vec<i32> },
}

impl BlockReader {
    /// Create a BlockReader from raw (decompressed) block bytes (borrows and copies).
    #[allow(dead_code)]
    pub fn create(block: &[u8]) -> io::Result<Self> {
        Self::create_from_vec(block.to_vec())
    }

    /// Create a BlockReader taking ownership of the decompressed block bytes (zero-copy).
    pub fn create_from_vec(mut block: Vec<u8>) -> io::Result<Self> {
        if block.is_empty() {
            return Ok(Self {
                data: Vec::new(),
                record_count: 0,
                seek_info: SeekInfo::Unaligned {
                    offsets: Vec::new(),
                },
            });
        }

        let aligned_type = BlockAlignedType::from_byte(block[block.len() - 1])?;
        let int_value = i32::from_le_bytes([
            block[block.len() - 5],
            block[block.len() - 4],
            block[block.len() - 3],
            block[block.len() - 2],
        ]);

        match aligned_type {
            BlockAlignedType::Aligned => {
                let record_size = int_value as usize;
                let data_len = block.len() - 5;
                let record_count = data_len.checked_div(record_size).unwrap_or(0);
                block.truncate(data_len);
                Ok(Self {
                    data: block,
                    record_count,
                    seek_info: SeekInfo::Aligned { record_size },
                })
            }
            BlockAlignedType::Unaligned => {
                let num_entries = int_value as usize;
                let index_len = num_entries * 4;
                let data_end = block.len() - 5 - index_len;
                let index_start = data_end;

                let mut offsets = Vec::with_capacity(num_entries);
                for i in 0..num_entries {
                    let pos = index_start + i * 4;
                    let off = i32::from_le_bytes([
                        block[pos],
                        block[pos + 1],
                        block[pos + 2],
                        block[pos + 3],
                    ]);
                    offsets.push(off);
                }

                block.truncate(data_end);
                Ok(Self {
                    data: block,
                    record_count: num_entries,
                    seek_info: SeekInfo::Unaligned { offsets },
                })
            }
        }
    }

    #[allow(dead_code)]
    pub fn record_count(&self) -> usize {
        self.record_count
    }

    fn seek_to_position(&self, record_pos: usize) -> usize {
        match &self.seek_info {
            SeekInfo::Aligned { record_size } => record_pos * record_size,
            SeekInfo::Unaligned { offsets } => offsets[record_pos] as usize,
        }
    }

    /// Read only the key at the given byte offset, skipping value parsing.
    /// Returns (key_slice, next_entry_offset).
    fn read_key_at(&self, offset: usize) -> (&[u8], usize) {
        let (key_len, consumed) = decode_var_int_from_slice(&self.data, offset);
        let key_start = offset + consumed;
        let key = &self.data[key_start..key_start + key_len as usize];

        let val_offset = key_start + key_len as usize;
        let (val_len, consumed2) = decode_var_int_from_slice(&self.data, val_offset);
        let next_offset = val_offset + consumed2 + val_len as usize;

        (key, next_offset)
    }

    /// Read a key-value entry at the given byte offset, returning (key, value, next_offset).
    /// Returns slices into the block's data buffer (zero-copy).
    pub(crate) fn read_entry_at(&self, offset: usize) -> (&[u8], &[u8], usize) {
        let (key_len, consumed) = decode_var_int_from_slice(&self.data, offset);
        let key_start = offset + consumed;
        let key = &self.data[key_start..key_start + key_len as usize];

        let val_offset = key_start + key_len as usize;
        let (val_len, consumed2) = decode_var_int_from_slice(&self.data, val_offset);
        let val_start = val_offset + consumed2;
        let value = &self.data[val_start..val_start + val_len as usize];

        (key, value, val_start + val_len as usize)
    }

    /// Create a sequential iterator over all entries.
    #[cfg(test)]
    pub fn iter(&self) -> BlockIter<'_> {
        BlockIter {
            reader: self,
            offset: 0,
            index: 0,
        }
    }

    /// Binary search for the given target key. Returns an iterator positioned at the
    /// first entry whose key >= target_key.
    /// The comparator compares two key byte slices.
    pub fn seek_and_iter<F>(&self, target_key: &[u8], cmp: &F) -> (bool, BlockIter<'_>)
    where
        F: Fn(&[u8], &[u8]) -> Ordering,
    {
        let mut left: i32 = 0;
        let mut right: i32 = self.record_count as i32 - 1;
        let mut found = false;
        let mut best_index: Option<usize> = None;
        let mut best_offset: Option<usize> = None;

        while left <= right {
            let mid = left + (right - left) / 2;
            let byte_offset = self.seek_to_position(mid as usize);
            let (key, _next_offset) = self.read_key_at(byte_offset);

            match cmp(key, target_key) {
                Ordering::Equal => {
                    found = true;
                    best_index = Some(mid as usize);
                    best_offset = Some(byte_offset);
                    break;
                }
                Ordering::Greater => {
                    best_index = Some(mid as usize);
                    best_offset = Some(byte_offset);
                    right = mid - 1;
                }
                Ordering::Less => {
                    left = mid + 1;
                }
            }
        }

        match (best_index, best_offset) {
            (Some(idx), Some(off)) => (
                found,
                BlockIter {
                    reader: self,
                    offset: off,
                    index: idx,
                },
            ),
            _ => (
                false,
                BlockIter {
                    reader: self,
                    offset: self.data.len(),
                    index: self.record_count,
                },
            ),
        }
    }
}

/// Iterator over block entries.
pub struct BlockIter<'a> {
    reader: &'a BlockReader,
    pub(crate) offset: usize,
    index: usize,
}

impl<'a> BlockIter<'a> {
    pub fn has_next(&self) -> bool {
        self.index < self.reader.record_count && self.offset < self.reader.data.len()
    }

    /// Returns (key, value) as borrowed slices (zero-copy).
    pub fn next(&mut self) -> Option<(&'a [u8], &'a [u8])> {
        if !self.has_next() {
            return None;
        }
        let (key, value, next_offset) = self.reader.read_entry_at(self.offset);
        self.offset = next_offset;
        self.index += 1;
        Some((key, value))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_block_roundtrip_aligned() {
        let mut writer = BlockWriter::new(1024);
        // All entries same size -> aligned
        writer.add(b"aa", b"11");
        writer.add(b"bb", b"22");
        writer.add(b"cc", b"33");

        let block = writer.finish();
        let reader = BlockReader::create(&block).unwrap();
        assert_eq!(reader.record_count(), 3);

        let entries: Vec<_> = {
            let mut iter = reader.iter();
            let mut v = Vec::new();
            while let Some(e) = iter.next() {
                v.push(e);
            }
            v
        };
        assert_eq!(entries[0], (&b"aa"[..], &b"11"[..]));
        assert_eq!(entries[1], (&b"bb"[..], &b"22"[..]));
        assert_eq!(entries[2], (&b"cc"[..], &b"33"[..]));
    }

    #[test]
    fn test_block_roundtrip_unaligned() {
        let mut writer = BlockWriter::new(1024);
        writer.add(b"a", b"1");
        writer.add(b"bb", b"22");
        writer.add(b"ccc", b"333");

        let block = writer.finish();
        let reader = BlockReader::create(&block).unwrap();
        assert_eq!(reader.record_count(), 3);

        let entries: Vec<_> = {
            let mut iter = reader.iter();
            let mut v = Vec::new();
            while let Some(e) = iter.next() {
                v.push(e);
            }
            v
        };
        assert_eq!(entries[0], (&b"a"[..], &b"1"[..]));
        assert_eq!(entries[1], (&b"bb"[..], &b"22"[..]));
        assert_eq!(entries[2], (&b"ccc"[..], &b"333"[..]));
    }

    #[test]
    fn test_block_seek() {
        let mut writer = BlockWriter::new(1024);
        writer.add(b"apple", b"1");
        writer.add(b"banana", b"2");
        writer.add(b"cherry", b"3");
        writer.add(b"date", b"4");

        let block = writer.finish();
        let reader = BlockReader::create(&block).unwrap();

        let cmp = |a: &[u8], b: &[u8]| a.cmp(b);

        // Exact match
        let (found, mut iter) = reader.seek_and_iter(b"banana", &cmp);
        assert!(found);
        let (k, v) = iter.next().unwrap();
        assert_eq!(k, b"banana");
        assert_eq!(v, b"2");

        // Seek to position >= "bz" -> should land on "cherry"
        let (found, mut iter) = reader.seek_and_iter(b"bz", &cmp);
        assert!(!found);
        let (k, _) = iter.next().unwrap();
        assert_eq!(k, b"cherry");

        // Seek past all entries
        let (found, iter) = reader.seek_and_iter(b"zzz", &cmp);
        assert!(!found);
        assert!(!iter.has_next());
    }

    #[test]
    fn test_block_handle_roundtrip() {
        let handle = BlockHandle::new(12345, 6789);
        let encoded = handle.encode();
        let decoded = BlockHandle::decode(&encoded).unwrap();
        assert_eq!(decoded.offset, 12345);
        assert_eq!(decoded.size, 6789);
    }

    #[test]
    fn test_block_trailer_roundtrip() {
        let trailer = BlockTrailer {
            compression_type: BlockCompressionType::None,
            crc32c: 0xDEADBEEF,
        };
        let mut buf = Vec::new();
        trailer.write_to(&mut buf).unwrap();
        assert_eq!(buf.len(), BLOCK_TRAILER_LENGTH);
        let decoded = BlockTrailer::read_from(&buf).unwrap();
        assert_eq!(decoded.compression_type, BlockCompressionType::None);
        assert_eq!(decoded.crc32c, 0xDEADBEEF);
    }

    #[test]
    fn test_crc32_compatible() {
        let data = b"hello world";
        let crc = compute_crc32(data, BlockCompressionType::None);
        // Just verify it's deterministic
        let crc2 = compute_crc32(data, BlockCompressionType::None);
        assert_eq!(crc, crc2);
    }
}
