use std::fs::{File, OpenOptions};
use std::os::unix::fs::FileExt;
use std::path::{Path, PathBuf};

use crate::error::DbResult;

const TAG_LEN: usize = 16;

/// Manages a `.tags` file that stores 16-byte GCM authentication tags
/// for each 4096-byte page in a corresponding `.data` file.
///
/// Layout: sequential 16-byte tags. Tag for page N is at byte offset `N * 16`.
pub struct TagFile {
    file: File,
}

impl TagFile {
    /// Open an existing tags file for reading.
    pub fn open_read(path: &Path) -> DbResult<Self> {
        let file = File::open(path)?;
        Ok(Self { file })
    }

    /// Open or create a tags file for read+write.
    pub fn open_write(path: &Path) -> DbResult<Self> {
        let file = OpenOptions::new()
            .create(true)
            .read(true)
            .write(true)
            .truncate(false)
            .open(path)?;
        Ok(Self { file })
    }

    /// Write consecutive tags starting at `start_page`.
    pub fn write_tags(&self, start_page: u64, tags: &[[u8; TAG_LEN]]) -> DbResult<()> {
        let offset = start_page * TAG_LEN as u64;
        let mut buf = Vec::with_capacity(tags.len() * TAG_LEN);
        for tag in tags {
            buf.extend_from_slice(tag);
        }
        self.file.write_all_at(&buf, offset)?;
        Ok(())
    }

    /// Read the tag for a single page.
    #[cfg(feature = "var-collections")]
    pub fn read_tag(&self, page_number: u64) -> DbResult<[u8; TAG_LEN]> {
        let offset = page_number * TAG_LEN as u64;
        let mut tag = [0u8; TAG_LEN];
        self.file.read_exact_at(&mut tag, offset)?;
        Ok(tag)
    }

    /// Read consecutive tags starting from `start_page`.
    pub fn read_tags(&self, start_page: u64, count: usize) -> DbResult<Vec<[u8; TAG_LEN]>> {
        let offset = start_page * TAG_LEN as u64;
        let total_len = count * TAG_LEN;
        let mut buf = vec![0u8; total_len];
        self.file.read_exact_at(&mut buf, offset)?;

        let mut tags = Vec::with_capacity(count);
        for chunk in buf.chunks_exact(TAG_LEN) {
            let mut tag = [0u8; TAG_LEN];
            tag.copy_from_slice(chunk);
            tags.push(tag);
        }
        Ok(tags)
    }

    /// Sync tag file data to disk.
    pub fn sync(&self) -> DbResult<()> {
        self.file.sync_data()?;
        Ok(())
    }
}

/// Compute the `.tags` path for a `.data` file path.
pub fn tags_path_for_data(data_path: &Path) -> PathBuf {
    data_path.with_extension("tags")
}
