package md5

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"io"
	"os"
	"strings"
)

// MD5结构体，可用于链式调用
type MD5 struct {
	data []byte
}

// New 创建一个新的MD5实例
func New() *MD5 {
	return &MD5{
		data: []byte{},
	}
}

// FromString 从字符串创建MD5
func FromString(s string) *MD5 {
	return &MD5{
		data: []byte(s),
	}
}

// FromBytes 从字节切片创建MD5
func FromBytes(b []byte) *MD5 {
	return &MD5{
		data: b,
	}
}

// Add 向MD5中添加字符串
func (m *MD5) Add(s string) *MD5 {
	m.data = append(m.data, []byte(s)...)
	return m
}

// AddBytes 向MD5中添加字节切片
func (m *MD5) AddBytes(b []byte) *MD5 {
	m.data = append(m.data, b...)
	return m
}

// Sum 计算并返回MD5哈希值(16进制字符串)
func (m *MD5) Sum() string {
	hash := md5.New()
	hash.Write(m.data)
	return hex.EncodeToString(hash.Sum(nil))
}

// SumBytes 计算并返回MD5哈希值(字节切片)
func (m *MD5) SumBytes() []byte {
	hash := md5.New()
	hash.Write(m.data)
	return hash.Sum(nil)
}

// 直接调用的工具函数

// EncryptString 加密字符串
func EncryptString(s string) string {
	hash := md5.New()
	hash.Write([]byte(s))
	return hex.EncodeToString(hash.Sum(nil))
}

// EncryptBytes 加密字节切片
func EncryptBytes(b []byte) string {
	hash := md5.New()
	hash.Write(b)
	return hex.EncodeToString(hash.Sum(nil))
}

// EncryptFile 加密文件内容
func EncryptFile(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()

	hash := md5.New()
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}
	return hex.EncodeToString(hash.Sum(nil)), nil
}

// EncryptFileChunk 对大文件分块计算MD5，提高效率
func EncryptFileChunk(filePath string, chunkSize int) (string, error) {
	if chunkSize <= 0 {
		chunkSize = 1024 * 1024 // 默认1MB
	}

	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()

	hash := md5.New()
	buf := make([]byte, chunkSize)
	reader := bufio.NewReader(file)

	for {
		n, err := reader.Read(buf)
		if err != nil && err != io.EOF {
			return "", err
		}
		if n == 0 {
			break
		}
		hash.Write(buf[:n])
	}

	return hex.EncodeToString(hash.Sum(nil)), nil
}

// EncryptStringWithSalt 使用盐值加密字符串
func EncryptStringWithSalt(s, salt string) string {
	return EncryptString(s + salt)
}

// EncryptStringWithPrefix 使用前缀加密字符串
func EncryptStringWithPrefix(s, prefix string) string {
	return EncryptString(prefix + s)
}

// VerifyMD5 验证字符串的MD5哈希是否匹配
func VerifyMD5(s, hash string) bool {
	return EncryptString(s) == strings.ToLower(hash)
}

// VerifyMD5WithSalt 验证带盐值的字符串MD5哈希是否匹配
func VerifyMD5WithSalt(s, salt, hash string) bool {
	return EncryptStringWithSalt(s, salt) == strings.ToLower(hash)
}

// VerifyFileMD5 验证文件的MD5哈希是否匹配
func VerifyFileMD5(filePath, hash string) (bool, error) {
	fileHash, err := EncryptFile(filePath)
	if err != nil {
		return false, err
	}
	return fileHash == strings.ToLower(hash), nil
}

// MD5格式化为指定位数

// Get16 获取16位MD5值(取32位结果的中间16位)
func Get16(s string) string {
	result := EncryptString(s)
	return result[8:24]
}

// Get8 获取8位MD5值
func Get8(s string) string {
	result := EncryptString(s)
	return result[12:20]
}

// MD5主要用于校验而非安全存储，对于需要高安全性的场景，应考虑:
// 1. bcrypt, scrypt或Argon2等专门为密码设计的算法
// 2. HMAC-MD5等方式以防御彩虹表攻击
// 3. 加盐并使用多次哈希迭代提高安全性

// MD5HMAC 使用HMAC-MD5算法
func MD5HMAC(message, key string) string {
	hash := md5.New()

	// 如果key长度超出block size，先进行哈希
	if len(key) > 64 {
		hash.Write([]byte(key))
		key = hex.EncodeToString(hash.Sum(nil))
		hash.Reset()
	}

	// 内部填充
	k_ipad := make([]byte, 64)
	k_opad := make([]byte, 64)
	copy(k_ipad, []byte(key))
	copy(k_opad, []byte(key))

	for i := 0; i < 64; i++ {
		k_ipad[i] ^= 0x36
		k_opad[i] ^= 0x5c
	}

	// 内部哈希
	hash.Write(k_ipad)
	hash.Write([]byte(message))
	innerHash := hash.Sum(nil)
	hash.Reset()

	// 外部哈希
	hash.Write(k_opad)
	hash.Write(innerHash)

	return hex.EncodeToString(hash.Sum(nil))
}
