use std::{
    io::{Cursor, Read},
    marker::PhantomData,
};

use ark_ec::pairing::Pairing;
use ark_serialize::SerializationError;
use byteorder::{LittleEndian, ReadBytesExt};
use thiserror::Error;

use crate::traits::CircomArkworksPairingBridge;

pub(crate) type ZkeyParserResult<T> = std::result::Result<T, ZkeyParserError>;

/// Error type describing errors during parsing zkey files
#[derive(Debug, Error)]
pub enum ZkeyParserError {
    /// Error during serialization
    #[error(transparent)]
    SerializationError(#[from] SerializationError),
    /// Error describing that an invalid modulus was found in the header for the chosen curve
    #[error("invalid modulus found in header for chosen curve")]
    InvalidPrimeInHeader,
    /// Unexpected Curve Size
    #[error("Unexpected field size for curve in header. Expected {0} but got {1}")]
    UnexpectedByteSize(u32, u32),
    /// Error during IO operations (reading/opening file, etc.)
    #[error(transparent)]
    IoError(#[from] std::io::Error),
    /// generic bin file error
    #[error("bin file corrupted: \"{0}\"")]
    CorruptedBinFile(String),
}

#[derive(Debug)]
pub(crate) struct BinFile<P: Pairing + CircomArkworksPairingBridge> {
    #[expect(dead_code)]
    ftype: String,
    #[expect(dead_code)]
    version: u32,
    sections: Vec<Vec<u8>>,
    phantom_data: PhantomData<P>,
}

impl<P: Pairing + CircomArkworksPairingBridge> BinFile<P> {
    pub(crate) fn new<R: Read>(reader: &mut R) -> ZkeyParserResult<Self> {
        tracing::debug!("reading bin file");
        let mut magic = [0u8; 4];
        reader.read_exact(&mut magic)?;
        let ftype = std::str::from_utf8(&magic[..])
            .map_err(|_| ZkeyParserError::CorruptedBinFile("cannot parse magic number".to_owned()))?
            .to_string();
        tracing::debug!("file type for binfile: \"{ftype}\"");

        let version = reader.read_u32::<LittleEndian>()?;
        tracing::debug!("binfile version {}", version);

        let num_sections: usize = reader
            .read_u32::<LittleEndian>()?
            .try_into()
            .expect("u32 fits into usize");
        tracing::debug!("we got {} sections in binfile", num_sections);
        let mut sections = vec![vec![]; num_sections];

        for _ in 0..num_sections {
            let section_id: usize = reader
                .read_u32::<LittleEndian>()?
                .try_into()
                .expect("u32 fits into usize");
            let section_length: usize = reader
                .read_u64::<LittleEndian>()?
                .try_into()
                .expect("u64 fits into usize");

            let section = &mut sections[section_id - 1];
            if !section.is_empty() {
                return Err(ZkeyParserError::CorruptedBinFile(
                    "sections are empty!".to_owned(),
                ));
            }
            section.resize(section_length, 0);
            reader.read_exact(section)?;
        }
        tracing::debug!("successfully read bin file!");
        Ok(Self {
            ftype,
            version,
            sections,
            phantom_data: PhantomData::<P>,
        })
    }

    pub(crate) fn take_section(&mut self, id: usize) -> Cursor<Vec<u8>> {
        Cursor::new(std::mem::take(&mut self.sections[id - 1]))
    }

    #[cfg(feature = "plonk")]
    pub(crate) fn take_section_raw(&mut self, id: usize) -> Vec<u8> {
        std::mem::take(&mut self.sections[id - 1])
    }
}
