use std::fs::{self, File};
use std::io::Read;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};

use anyhow::{anyhow, Context, Result};
use ed2k::digest::Digest;
use ed2k::Ed2k;
use md5::Md5;
use serde::{Deserialize, Serialize};
use sha1::Sha1;
use sha2::Sha256;
use xxhash_rust::xxh3::Xxh3;

use crate::constants::{DEFAULT_BUFFER_SIZE, HEAD_115_BYTES, HEAD_BAIDU_BYTES};
use crate::head_hash::{calc_head_115, calc_head_baidu, HeadChunk};
use crate::utils::{basename, friendly_size, hex_upper};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileMeta {
    pub basename: String,
    pub size: u64,
    pub friendly_size: String,
    pub mtime: i64,
    pub head_115: String,
    pub head_baidu: String,
    pub ed2k: String,
    pub md5: String,
    pub sha1: String,
    pub sha256: String,
    pub xxh128: String,
}

impl FileMeta {
    pub fn from_path_with_callback<F1, F2>(path: &Path, mut on_bytes_read: F1, mut on_iop: F2) -> Result<Self>
    where
        F1: FnMut(u64),
        F2: FnMut(),
    {
        let info =
            fs::metadata(path).with_context(|| format!("无法读取文件信息: {}", path.display()))?;
        if !info.is_file() {
            return Err(anyhow!("{} 不是文件", path.display()));
        }

        let basename_str = basename(
            path.file_name()
                .ok_or_else(|| anyhow!("{} 缺少文件名", path.display()))?,
        );
        let size = info.len();
        let friendly = friendly_size(size);
        let mtime = info
            .modified()
            .unwrap_or(SystemTime::UNIX_EPOCH)
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_secs() as i64)
            .unwrap_or(0);

        let mut file =
            File::open(path).with_context(|| format!("无法打开文件: {}", path.display()))?;

        let mut buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
        let mut md5_hasher = Md5::new();
        let mut sha1_hasher = Sha1::new();
        let mut sha256_hasher = Sha256::new();
        let mut xxh_hasher = Xxh3::new();
        let mut ed2k_hasher = Ed2k::new();
        let mut head115 = HeadChunk::new(HEAD_115_BYTES);
        let mut head_baidu = HeadChunk::new(HEAD_BAIDU_BYTES);

        loop {
            let read_len = file.read(&mut buffer)?;
            if read_len == 0 {
                break;
            }
            let chunk = &buffer[..read_len];
            md5_hasher.update(chunk);
            sha1_hasher.update(chunk);
            sha256_hasher.update(chunk);
            xxh_hasher.update(chunk);
            ed2k_hasher.update(chunk);

            head115.feed(chunk);
            head_baidu.feed(chunk);

            on_bytes_read(read_len as u64);
            on_iop(); // 每次 read 调用算一次 IOPS
        }

        let head_115 = calc_head_115(head115.as_slice());
        let head_baidu = calc_head_baidu(head_baidu.as_slice());

        let md5_hex = hex_upper(md5_hasher.finalize());
        let sha1_hex = hex_upper(sha1_hasher.finalize());
        let sha256_hex = hex_upper(sha256_hasher.finalize());
        let xxh_hex = hex_upper(xxh_hasher.digest128().to_be_bytes());
        let ed2k_hex = hex_upper(ed2k_hasher.finalize());

        Ok(Self {
            basename: basename_str,
            size,
            friendly_size: friendly,
            mtime,
            head_115,
            head_baidu,
            ed2k: ed2k_hex,
            md5: md5_hex,
            sha1: sha1_hex,
            sha256: sha256_hex,
            xxh128: xxh_hex,
        })
    }

    pub fn from_reader<R: Read>(reader: R) -> Result<Self> {
        Ok(serde_json::from_reader(reader)?)
    }

    pub fn to_pretty_json(&self) -> Result<String> {
        Ok(serde_json::to_string_pretty(self)?)
    }
}

pub fn calc_xxh128_with_callback<F1, F2>(path: &Path, mut on_bytes_read: F1, mut on_iop: F2) -> Result<String>
where
    F1: FnMut(u64),
    F2: FnMut(),
{
    let mut file = File::open(path).with_context(|| format!("无法打开文件: {}", path.display()))?;
    let mut buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
    let mut hasher = Xxh3::new();

    loop {
        let read_len = file.read(&mut buffer)?;
        if read_len == 0 {
            break;
        }
        hasher.update(&buffer[..read_len]);
        on_bytes_read(read_len as u64);
        on_iop(); // 每次 read 调用算一次 IOPS
    }

    Ok(hex_upper(hasher.digest128().to_be_bytes()))
}
