#[derive(Debug, PartialEq)]
pub enum Base64Error {
    InvalidCharacter(char),
    InvalidPadding,
}

pub struct Base64<'a> {
    pub data: &'a [u8],
}

impl<'a> Base64<'a> {
    /// Encodes a byte slice into a base64-encoded string.
    ///
    /// This function implements standard base64 encoding, converting each group of 3 input bytes into
    /// 4 output characters from the base64 alphabet (A-Z, a-z, 0-9, +, /). If the input length is not
    /// a multiple of 3, padding with '=' characters is added as needed.
    ///
    /// # Parameters
    ///
    /// - `data`: A slice of bytes to encode.
    ///
    /// # Returns
    ///
    /// A `String` containing the base64-encoded representation of the input data.
    ///
    /// # Example
    ///
    /// ```rust
    /// use qirust::base64::Base64;
    ///
    /// let data = b"Hello";
    /// let encoded = Base64::encode_base64(data);
    /// assert_eq!(encoded, "SGVsbG8=");
    /// ```
    pub fn encode_base64(data: &[u8]) -> String {
        const BASE64_ALPHABET: &[
            u8;
            64
        ] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        let mut result = String::new();

        for chunk in data.chunks(3) {
            let b1 = chunk[0];
            let b2 = chunk.get(1).copied().unwrap_or(0);
            let b3 = chunk.get(2).copied().unwrap_or(0);

            let c1 = (b1 >> 2) & 0x3f;
            let c2 = ((b1 & 0x03) << 4) | ((b2 >> 4) & 0x0f);
            let c3 = if chunk.len() > 1 { ((b2 & 0x0f) << 2) | ((b3 >> 6) & 0x03) } else { 64 };
            let c4 = if chunk.len() > 2 { b3 & 0x3f } else { 64 };

            result.push(BASE64_ALPHABET[c1 as usize] as char);
            result.push(BASE64_ALPHABET[c2 as usize] as char);
            result.push(if c3 == 64 { '=' } else { BASE64_ALPHABET[c3 as usize] as char });
            result.push(if c4 == 64 { '=' } else { BASE64_ALPHABET[c4 as usize] as char });
        }

        result
    }

    /// Decodes a base64-encoded string into a vector of bytes.
    ///
    /// This function implements standard base64 decoding, converting each group of 4 base64 characters
    /// (A-Z, a-z, 0-9, +, /) into 3 output bytes. It validates input and returns errors for invalid
    /// characters or improper padding.
    ///
    /// # Parameters
    ///
    /// - `data`: A string containing base64-encoded data.
    ///
    /// # Returns
    ///
    /// A `Result<Vec<u8>, Base64Error>` containing the decoded bytes or an error.
    ///
    /// # Errors
    ///
    /// - `InvalidCharacter`: If a character is not part of the base64 alphabet (A-Z, a-z, 0-9, +, /, =).
    /// - `InvalidPadding`: If padding characters ('=') are not at the end or are incorrectly used.
    ///
    /// # Example
    ///
    /// ```rust
    /// use qirust::base64::{Base64, Base64Error};
    ///
    /// let encoded = "SGVsbG8=";
    /// let decoded = Base64::decode_base64(encoded).unwrap();
    /// assert_eq!(decoded, b"Hello".to_vec());
    /// ```
    pub fn decode_base64(data: &str) -> Result<Vec<u8>, Base64Error> {
        let mut result = Vec::new();
        let mut group = [0u8; 4];
        let mut group_index = 0;
        let mut padding_count = 0;

        let chars: Vec<char> = data.chars().collect();
        let len = chars.len();

        for (i, c) in chars.iter().enumerate() {
            if c == &'=' {
                padding_count += 1;
                if i < len - 2 {
                    return Err(Base64Error::InvalidPadding);
                }
                if group_index > 0 {
                    let mut buffer = 0u32;
                    for j in 0..group_index {
                        buffer = (buffer << 6) | (group[j] as u32);
                    }
                    match group_index {
                        2 => {
                            result.push((buffer >> 4) as u8);
                        }
                        3 => {
                            result.push((buffer >> 10) as u8);
                            result.push((buffer >> 2) as u8);
                        }
                        4 => {
                            result.push((buffer >> 16) as u8);
                            result.push((buffer >> 8) as u8);
                            result.push(buffer as u8);
                        }
                        _ => {
                            return Err(Base64Error::InvalidPadding);
                        }
                    }
                }
                break;
            }

            if padding_count > 0 {
                return Err(Base64Error::InvalidPadding);
            }

            let value = match c {
                'A'..='Z' => (*c as u8) - b'A',
                'a'..='z' => (*c as u8) - b'a' + 26,
                '0'..='9' => (*c as u8) - b'0' + 52,
                '+' => 62,
                '/' => 63,
                _ => {
                    return Err(Base64Error::InvalidCharacter(*c));
                }
            };

            group[group_index] = value;
            group_index += 1;

            if group_index == 4 {
                let buffer =
                    ((group[0] as u32) << 18) |
                    ((group[1] as u32) << 12) |
                    ((group[2] as u32) << 6) |
                    (group[3] as u32);
                result.push((buffer >> 16) as u8);
                result.push((buffer >> 8) as u8);
                result.push(buffer as u8);
                group_index = 0;
            }
        }

        if group_index > 0 && padding_count == 0 {
            let mut buffer = 0u32;
            for j in 0..group_index {
                buffer = (buffer << 6) | (group[j] as u32);
            }
            match group_index {
                2 => {
                    result.push((buffer >> 4) as u8);
                }
                3 => {
                    result.push((buffer >> 10) as u8);
                    result.push((buffer >> 2) as u8);
                }
                _ => {
                    return Err(Base64Error::InvalidPadding);
                }
            }
        }

        if padding_count > 2 {
            return Err(Base64Error::InvalidPadding);
        }

        Ok(result)
    }
}

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

    #[test]
    fn test_encode_base64_empty() {
        let data = b"";
        let encoded = Base64::encode_base64(data);
        assert_eq!(encoded, "");
    }

    #[test]
    fn test_encode_base64_single_byte() {
        let data = b"A";
        let encoded = Base64::encode_base64(data);
        assert_eq!(encoded, "QQ==");
    }

    #[test]
    fn test_encode_base64_two_bytes() {
        let data = b"Hi";
        let encoded = Base64::encode_base64(data);
        assert_eq!(encoded, "SGk=");
    }

    #[test]
    fn test_encode_base64_large_input() {
        let data = vec![0xFF; 100];
        let encoded = Base64::encode_base64(&data);
        let expected =
            "////////////////////////////////////\
                        ////////////////////////////////////\
                        ////////////////////////////////////\
                        ///////w=="; // 132 '/' + '/w==' = 136 chars
        assert_eq!(encoded, expected);
        let decoded = Base64::decode_base64(&encoded).unwrap();
        assert_eq!(decoded, data);
    }

    #[test]
    fn test_decode_base64_valid() {
        let encoded = "SGVsbG8=";
        let decoded = Base64::decode_base64(encoded).unwrap();
        assert_eq!(decoded, b"Hello".to_vec());
    }

    #[test]
    fn test_decode_base64_no_padding() {
        let encoded = "SGVsbG8";
        let decoded = Base64::decode_base64(encoded).unwrap();
        assert_eq!(decoded, b"Hello".to_vec());
    }

    #[test]
    fn test_decode_base64_empty() {
        let encoded = "";
        let decoded = Base64::decode_base64(encoded).unwrap();
        assert_eq!(decoded, vec![]);
    }

    #[test]
    fn test_decode_base64_invalid_char() {
        let encoded = "SGVsb@G8=";
        assert_eq!(Base64::decode_base64(encoded), Err(Base64Error::InvalidCharacter('@')));
    }

    #[test]
    fn test_decode_base64_invalid_padding() {
        let encoded = "SGVsbG8=SGVsbG8=";
        assert_eq!(Base64::decode_base64(encoded), Err(Base64Error::InvalidPadding));
    }

    #[test]
    fn test_decode_base64_two_chars() {
        let encoded = "SG==";
        let decoded = Base64::decode_base64(encoded).unwrap();
        assert_eq!(decoded, b"H".to_vec());
    }

    #[test]
    fn test_decode_base64_one_char() {
        let encoded = "QQ==";
        let decoded = Base64::decode_base64(encoded).unwrap();
        assert_eq!(decoded, b"A".to_vec());
    }

    #[test]
    fn test_decode_base64_three_chars() {
        let encoded = "SGk=";
        let decoded = Base64::decode_base64(encoded).unwrap();
        assert_eq!(decoded, b"Hi".to_vec());
    }

    #[test]
    fn test_decode_base64_invalid_single_char() {
        let encoded = "Q===";
        assert_eq!(Base64::decode_base64(encoded), Err(Base64Error::InvalidPadding));
    }

    #[test]
    fn test_decode_base64_whitespace() {
        let encoded = "SG VsbG8=";
        assert_eq!(Base64::decode_base64(encoded), Err(Base64Error::InvalidCharacter(' ')));
    }

    #[test]
    fn test_decode_base64_excessive_padding() {
        let encoded = "SG===";
        assert_eq!(Base64::decode_base64(encoded), Err(Base64Error::InvalidPadding));
    }

    #[test]
    fn test_decode_base64_padding_in_middle() {
        let encoded = "SG==VsbG8=";
        assert_eq!(Base64::decode_base64(encoded), Err(Base64Error::InvalidPadding));
    }

    #[test]
    fn test_decode_base64_long_input() {
        let data = vec![0xFF; 100];
        let encoded = Base64::encode_base64(&data);
        let decoded = Base64::decode_base64(&encoded).unwrap();
        assert_eq!(decoded, data);
    }
}
