//
// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com)
//
// This file is part of the Bichon Email Archiving Project
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

use std::{fs, io, path::PathBuf};

use crate::error::BichonResult;
use base64::engine::general_purpose::STANDARD;
use base64::{engine::general_purpose, Engine};
use rand::{rng, RngExt};

use super::error::code::ErrorCode;

pub mod encrypt;
pub mod html;
pub mod net;
pub mod rate_limit;
pub mod shutdown;
pub mod tls;

#[macro_export]
macro_rules! bichon_version {
    () => {
        env!("CARGO_PKG_VERSION")
    };
}

#[macro_export]
macro_rules! utc_now {
    () => {{
        use chrono::Utc;
        Utc::now().timestamp_millis()
    }};
}

#[macro_export]
macro_rules! after_n_days_timestamp {
    ($start_ts:expr, $days:expr) => {{
        const MILLIS_PER_DAY: i64 = 86_400_000; // 24 * 60 * 60 * 1000
        $start_ts + ($days as i64) * MILLIS_PER_DAY
    }};
}

#[macro_export]
macro_rules! base64_encode {
    ($bytes:expr) => {{
        use base64::{engine::general_purpose::STANDARD, *};
        STANDARD.encode($bytes)
    }};
}

#[macro_export]
macro_rules! base64_decode {
    ($key:expr) => {{
        use base64::{engine::general_purpose::STANDARD, *};
        STANDARD.decode($key).unwrap()
    }};
}

#[macro_export]
macro_rules! base64_decode_url_safe {
    ($key:expr) => {{
        use base64::{engine::general_purpose::URL_SAFE, *};
        URL_SAFE.decode($key)
    }};
}

#[macro_export]
macro_rules! base64_encode_url_safe {
    ($key:expr) => {{
        use base64::{engine::general_purpose::URL_SAFE, *};
        URL_SAFE.encode($key)
    }};
}

#[macro_export]
macro_rules! product_public_key {
    () => {
        $crate::base64_decode!(r#"BNlT+WjdEls9VGfry+zKygx+UoypxSqsMBddMGxYgbhWOz7Xfh7YJXGMeby9jBtbz3rhSGrTuZCYA9uwwMMYkhI="#)
    };
}

#[macro_export]
macro_rules! license_header {
    () => {
        "{\"alg\":\"ES256\",\"typ\":\"JWT\"}"
    };
}

#[macro_export]
macro_rules! raise_error {
    ($msg:expr, $code:expr) => {
        $crate::error::BichonError::Generic {
            message: $msg,
            code: $code,
            location: snafu::location!(),
        }
    };
}

#[macro_export]
macro_rules! run_with_timeout {
    ($duration:expr, $task:expr, $err_msg:expr) => {{
        match tokio::time::timeout($duration, $task).await {
            Ok(result) => Ok(result),
            Err(_) => Err($err_msg),
        }
    }};
}

#[macro_export]
macro_rules! free_memory {
    () => {{
        let mut sys = sysinfo::System::new_all();
        sys.refresh_memory();
        sys.free_memory()
    }};
}

#[macro_export]
macro_rules! generate_token {
    ($bit_strength:expr) => {{
        $crate::utils::generate_token_impl($bit_strength)
    }};
}

pub(crate) fn generate_token_impl(bit_strength: usize) -> String {
    let byte_length = (bit_strength + 23) / 24 * 3;
    let random_bytes: Vec<u8> = (0..byte_length).map(|_| rand::random::<u8>()).collect();
    let mut encoded = general_purpose::URL_SAFE.encode(&random_bytes);

    encoded = encoded
        .chars()
        .map(|c| {
            if c == '/' || c == '+' || c == '-' || c == '_' {
                make_single_random_char()
            } else {
                c
            }
        })
        .collect();

    encoded
}

fn make_single_random_char() -> char {
    let random_bytes: [u8; 3] = rng().random();
    let encoded = general_purpose::URL_SAFE.encode(random_bytes);
    encoded
        .chars()
        .find(|&c| c != '-' && c != '_' && c != '+' && c != '/')
        .unwrap_or('a')
}

#[macro_export]
macro_rules! ensure_access {
    ($dir:expr) => {{
        $crate::utils::ensure_dir_and_test_access($dir)
    }};
}

#[macro_export]
macro_rules! decode_mailbox_name {
    ($name:expr) => {{
        utf7_imap::decode_utf7_imap($name.to_string())
    }};
}
#[macro_export]
macro_rules! encode_mailbox_name {
    ($name:expr) => {{
        utf7_imap::encode_utf7_imap($name.to_string())
    }};
}

#[macro_export]
macro_rules! get_encoding {
    ($label:expr) => {
        match encoding_rs::Encoding::for_label($label.as_bytes()) {
            None => None,
            Some(encoding) => Some(encoding),
        }
    };
}

#[macro_export]
macro_rules! current_datetime {
    () => {{
        use chrono::Local;
        let now = Local::now();
        now.format("%Y%m%d%H%M").to_string()
    }};
}

#[macro_export]
macro_rules! validate_email {
    ($email:expr) => {{
        $crate::utils::validate_email($email)
    }};
}

#[macro_export]
macro_rules! encrypt {
    ($plaintext:expr) => {{
        $crate::utils::encrypt::encrypt_string($plaintext)
    }};
}

#[macro_export]
macro_rules! decrypt {
    ($plaintext:expr) => {{
        $crate::utils::encrypt::decrypt_string($plaintext)
    }};
}

pub fn validate_email(email: &str) -> crate::error::BichonResult<()> {
    use std::str::FromStr;
    let email_address = email_address::EmailAddress::from_str(email).map_err(|_| {
        raise_error!(
            format!("Invalid email format : {}", email),
            ErrorCode::InvalidParameter
        )
    })?;
    if email != email_address.email() {
        return Err(raise_error!(
            format!("Invalid email format: {}", email),
            ErrorCode::InvalidParameter
        ));
    }
    Ok(())
}

#[macro_export]
macro_rules! id {
    ($bit_strength:expr) => {{
        // Generate a token with the given bit strength
        let token = $crate::utils::generate_token_impl($bit_strength);
        // Hash the generated token
        $crate::utils::hash(&token)
    }};
}

#[macro_export]
macro_rules! u64_to_str {
    ($id:expr) => {{
        let mut buf = itoa::Buffer::new();
        buf.format($id)
    }};
}

/// Generates a 64-bit hash from a string, ensuring the output is within JavaScript's safe integer range (0 to 2^53 - 1).
pub fn hash(s: &str) -> u64 {
    let mut cursor = Vec::new();
    cursor.extend_from_slice(s.as_bytes());
    let mut cursor = std::io::Cursor::new(cursor);
    let hash = murmur3::murmur3_x64_128(&mut cursor, 0).unwrap();
    (hash & 0x1F_FFFF_FFFF_FFFF) as u64
}

pub fn hex_hash(s: &str) -> String {
    let mut cursor = Vec::new();
    cursor.extend_from_slice(s.as_bytes());
    let mut cursor = std::io::Cursor::new(cursor);
    let hash = murmur3::murmur3_x64_128(&mut cursor, 0).unwrap();
    format!("{:032x}", hash)
}

pub fn create_hash(account_id: u64, field: &str) -> u64 {
    let mut buffer = Vec::new();
    buffer.extend_from_slice(&account_id.to_le_bytes());
    buffer.push(b':'); // Separator
    buffer.extend_from_slice(field.as_bytes());
    let mut cursor = std::io::Cursor::new(buffer);
    let hash = murmur3::murmur3_x64_128(&mut cursor, 0).unwrap();
    (hash & 0x1F_FFFF_FFFF_FFFF) as u64
}

pub fn create_hash2(account_id: u64, field1: u64, field2: &str) -> u64 {
    let mut buffer = Vec::new();
    buffer.extend_from_slice(&account_id.to_le_bytes());
    buffer.push(b':'); // Separator
    buffer.extend_from_slice(&field1.to_le_bytes());
    buffer.push(b':'); // Separator
    buffer.extend_from_slice(field2.as_bytes());
    let mut cursor = std::io::Cursor::new(buffer);
    let hash = murmur3::murmur3_x64_128(&mut cursor, 0).unwrap();
    (hash & 0x1F_FFFF_FFFF_FFFF) as u64
}

pub fn get_total_size(path: &PathBuf) -> io::Result<u64> {
    if !path.exists() {
        return Ok(0);
    }

    if path.is_file() {
        return Ok(fs::metadata(path)?.len());
    }

    let mut total_size = 0u64;
    for entry in fs::read_dir(path)? {
        let entry = entry?;
        let entry_path = entry.path();

        if entry_path.is_file() {
            total_size += fs::metadata(&entry_path)?.len();
        } else if entry_path.is_dir() {
            total_size += get_total_size(&entry_path)?;
        }
    }

    Ok(total_size)
}

const MAX_AVATAR_BYTES: usize = 128 * 1024;

pub fn decode_avatar_bytes(base64_str: &str) -> BichonResult<Vec<u8>> {
    let bytes = STANDARD.decode(base64_str).map_err(|e| {
        raise_error!(
            format!("Invalid avatar base64 encoding: {}", e),
            ErrorCode::InvalidParameter
        )
    })?;

    if bytes.len() > MAX_AVATAR_BYTES {
        return Err(raise_error!(
            format!(
                "Avatar image exceeds maximum size ({} KB).",
                MAX_AVATAR_BYTES / 1024
            ),
            ErrorCode::InvalidParameter
        ));
    }

    Ok(bytes)
}

pub fn compute_content_hash(content: &[u8]) -> String {
    let hash = blake3::hash(content);
    hash.to_hex().to_string()
}

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

    // ── hash ──────────────────────────────────────────────────────────

    #[test]
    fn hash_is_deterministic() {
        let a = hash("hello world");
        let b = hash("hello world");
        assert_eq!(a, b);
    }

    #[test]
    fn hash_different_inputs_produce_different_outputs() {
        let a = hash("hello");
        let b = hash("world");
        assert_ne!(a, b);
    }

    #[test]
    fn hash_empty_string() {
        let h = hash("");
        assert!(h < (1u64 << 53));
    }

    #[test]
    fn hex_hash_is_deterministic() {
        let a = hex_hash("test");
        let b = hex_hash("test");
        assert_eq!(a, b);
        assert_eq!(a.len(), 32);
    }

    #[test]
    fn hex_hash_different_inputs_produce_different_outputs() {
        assert_ne!(hex_hash("a"), hex_hash("b"));
    }

    // ── create_hash / create_hash2 ────────────────────────────────────

    #[test]
    fn create_hash_deterministic() {
        let a = create_hash(1, "INBOX");
        let b = create_hash(1, "INBOX");
        assert_eq!(a, b);
    }

    #[test]
    fn create_hash_different_accounts_differ() {
        assert_ne!(create_hash(1, "INBOX"), create_hash(2, "INBOX"));
    }

    #[test]
    fn create_hash_different_fields_differ() {
        assert_ne!(create_hash(1, "INBOX"), create_hash(1, "Sent"));
    }

    #[test]
    fn create_hash2_deterministic() {
        let a = create_hash2(1, 100, "INBOX");
        let b = create_hash2(1, 100, "INBOX");
        assert_eq!(a, b);
    }

    #[test]
    fn create_hash2_different_inputs_differ() {
        assert_ne!(create_hash2(1, 100, "INBOX"), create_hash2(1, 200, "INBOX"));
        assert_ne!(create_hash2(1, 100, "INBOX"), create_hash2(1, 100, "Sent"));
    }

    // ── compute_content_hash ──────────────────────────────────────────

    #[test]
    fn compute_content_hash_deterministic() {
        let data = b"test content";
        let a = compute_content_hash(data);
        let b = compute_content_hash(data);
        assert_eq!(a, b);
        assert_eq!(a.len(), 64);
    }

    #[test]
    fn compute_content_hash_different_content_differ() {
        assert_ne!(compute_content_hash(b"a"), compute_content_hash(b"b"));
    }

    // ── validate_email ────────────────────────────────────────────────

    #[test]
    fn validate_email_valid() {
        assert!(validate_email("user@example.com").is_ok());
        assert!(validate_email("a@b.co").is_ok());
        assert!(validate_email("test.user+tag@domain.com").is_ok());
    }

    #[test]
    fn validate_email_invalid() {
        assert!(validate_email("not-an-email").is_err());
        assert!(validate_email("").is_err());
        assert!(validate_email("@domain.com").is_err());
        assert!(validate_email("user@").is_err());
    }

    // ── generate_token_impl ───────────────────────────────────────────

    #[test]
    fn generate_token_has_expected_length() {
        let token = generate_token_impl(256);
        // URL-safe base64 encodes 3 bytes → 4 chars, so length is roughly
        // ceil(bit_strength / 24) * 4, but chars like /+=-_ are replaced
        assert!(!token.is_empty());
    }

    #[test]
    fn generate_token_does_not_contain_special_chars() {
        for _ in 0..10 {
            let token = generate_token_impl(256);
            assert!(!token.contains('/'));
            assert!(!token.contains('+'));
            assert!(!token.contains('-'));
            assert!(!token.contains('_'));
        }
    }

    #[test]
    fn generate_token_is_random() {
        let a = generate_token_impl(256);
        let b = generate_token_impl(256);
        assert_ne!(a, b);
    }

    // ── decode_avatar_bytes ───────────────────────────────────────────

    #[test]
    fn decode_avatar_bytes_valid() {
        // "avatar" in base64 = "YXZhdGFy"
        let result = decode_avatar_bytes("YXZhdGFy").unwrap();
        assert_eq!(result, b"avatar");
    }

    #[test]
    fn decode_avatar_bytes_invalid_base64() {
        assert!(decode_avatar_bytes("!!!invalid!!!").is_err());
    }
}
