# 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.

"""Reader for a block."""

import struct
from typing import Callable, Optional

from pypaimon.globalindex.btree.block_aligned_type import BlockAlignedType
from pypaimon.globalindex.btree.block_entry import BlockEntry
from pypaimon.globalindex.memory_slice_input import MemorySliceInput


class BlockReader:
    """Reader for a block."""
    
    def __init__(
        self,
        block: bytes,
        record_count: int,
        comparator: Callable[[bytes, bytes], int]
    ):
        """Initialize BlockReader.
        
        Args:
            block: The block data bytes
            record_count: Number of records in the block
            comparator: Optional comparator function for keys
        """
        self.block = block
        self.record_count = record_count
        self.comparator = comparator
    
    def block_input(self) -> MemorySliceInput:
        """Create a MemorySliceInput for this block."""
        return MemorySliceInput(self.block)
    
    def iterator(self) -> 'BlockIterator':
        """Create a BlockIterator for this reader."""
        return BlockIterator(self)
    
    def seek_to(self, record_position: int) -> int:
        """Seek to slice position from record position.
        
        Args:
            record_position: The record position to seek to
            
        Returns:
            The slice position
            
        Raises:
            NotImplementedError: If not implemented in subclass
        """
        raise NotImplementedError("seekTo must be implemented in subclass")
    
    @staticmethod
    def create(
        block: bytes,
        comparator: Optional[Callable[[bytes, bytes], int]] = None
    ) -> 'BlockReader':
        """Create a BlockReader from block data.
        
        Args:
            block: The block data bytes
            comparator: Optional comparator function for keys
            
        Returns:
            A BlockReader instance (AlignedBlockReader or UnalignedBlockReader)
        """
        # Read block trailer: last byte is aligned type, previous 4 bytes is record size or index length
        aligned_type_byte = block[-1]
        aligned_type = BlockAlignedType.from_byte(aligned_type_byte)
        int_value = struct.unpack('<I', block[-5:-1])[0]
        
        if aligned_type == BlockAlignedType.ALIGNED:
            # Aligned block: records have fixed size
            data = block[:-5]
            return AlignedBlockReader(data, int_value, comparator)
        else:
            # Unaligned block: uses index
            index_length = int_value * 4
            index_offset = len(block) - 5 - index_length
            data = block[:index_offset]
            index = block[index_offset:index_offset + index_length]
            return UnalignedBlockReader(data, index, comparator)


class AlignedBlockReader(BlockReader):
    """Block reader for aligned blocks (fixed record size)."""
    
    def __init__(
        self,
        data: bytes,
        record_size: int,
        comparator: Optional[Callable[[bytes, bytes], int]] = None
    ):
        """Initialize AlignedBlockReader.
        
        Args:
            data: The block data bytes
            record_size: The fixed size of each record
            comparator: Optional comparator function for keys
        """
        record_count = len(data) // record_size
        super().__init__(data, record_count, comparator)
        self.record_size = record_size
    
    def seek_to(self, record_position: int) -> int:
        """Seek to slice position from record position.
        
        Args:
            record_position: The record position to seek to
            
        Returns:
            The slice position
        """
        return record_position * self.record_size


class UnalignedBlockReader(BlockReader):
    """Block reader for unaligned blocks (uses index)."""
    
    def __init__(
        self,
        data: bytes,
        index: bytes,
        comparator: Optional[Callable[[bytes, bytes], int]] = None
    ):
        """Initialize UnalignedBlockReader.
        
        Args:
            data: The block data bytes
            index: The index bytes (4 bytes per record)
            comparator: Optional comparator function for keys
        """
        record_count = len(index) // 4
        super().__init__(data, record_count, comparator)
        self.index = index
    
    def seek_to(self, record_position: int) -> int:
        """Seek to slice position from record position.
        
        Args:
            record_position: The record position to seek to
            
        Returns:
            The slice position
        """
        # Read 4-byte integer from index at record_position * 4
        offset = record_position * 4
        return struct.unpack('<I', self.index[offset:offset + 4])[0]


class BlockIterator:
    """Iterator for block entries."""
    
    def __init__(self, reader: BlockReader):
        """Initialize BlockIterator.
        
        Args:
            reader: The BlockReader to iterate over
        """
        self.reader = reader
        self.input = reader.block_input()
        self.polled: Optional[BlockEntry] = None
    
    def __iter__(self):
        """Return self as iterator."""
        return self
    
    def __next__(self) -> BlockEntry:
        """Get next entry.
        
        Returns:
            The next BlockEntry
            
        Raises:
            StopIteration: If no more entries
        """
        if not self.has_next():
            raise StopIteration
        
        if self.polled is not None:
            result = self.polled
            self.polled = None
            return result
        
        return self.read_entry()
    
    def has_next(self) -> bool:
        """Check if there are more entries."""
        return self.polled is not None or self.input.is_readable()
    
    def seek_to(self, target_key: bytes) -> bool:
        """Seek to the first key >= target_key using binary search.
        
        Args:
            target_key: The target key to seek to
            
        Returns:
            True if exact match found, False otherwise
        """
        left = 0
        right = self.reader.record_count - 1
        
        while left <= right:
            mid = left + (right - left) // 2
            
            self.input.set_position(self.reader.seek_to(mid))
            mid_entry = self.read_entry()
            compare = self.reader.comparator(mid_entry.key, target_key)
            
            if compare == 0:
                self.polled = mid_entry
                return True
            elif compare > 0:
                self.polled = mid_entry
                right = mid - 1
            else:
                self.polled = None
                left = mid + 1
        
        return False
    
    def read_entry(self) -> BlockEntry:
        """Read a key-value entry.
        
        Returns:
            A BlockEntry containing key and value
        """
        # Read key
        key_length = self.input.read_var_len_int()
        key = self.input.read_slice(key_length)
        
        # Read value
        value_length = self.input.read_var_len_int()
        value = self.input.read_slice(value_length)
        
        return BlockEntry(key, value)
