import hashlib
import hmac
import zlib
import struct
import os
import json

import bcrypt as _bcrypt_lib
from passlib.hash import apr_md5_crypt, des_crypt
from Crypto.Hash import MD4 as CryptoMD4


HASH_ALGORITHMS = {
    "adler32": {
        "name": "Adler32",
        "supports_hmac": False,
        "supports_salt": False,
    },
    "htpasswd-apache": {
        "name": "htpasswd Apache",
        "supports_hmac": False,
        "supports_salt": True,
    },
    "blowfish": {
        "name": "Blowfish",
        "supports_hmac": False,
        "supports_salt": False,
    },
    "crc-32": {
        "name": "CRC-32",
        "supports_hmac": False,
        "supports_salt": False,
    },
    "crc-32b": {
        "name": "CRC-32B",
        "supports_hmac": False,
        "supports_salt": False,
    },
    "des": {
        "name": "DES",
        "supports_hmac": False,
        "supports_salt": True,
    },
    "gost": {
        "name": "GOST",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "haval-128": {
        "name": "Haval-128",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "md4": {
        "name": "MD4",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "md5": {
        "name": "MD5",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "ripemd-128": {
        "name": "RIPEMD-128",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "ripemd-160": {
        "name": "RIPEMD-160",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "sha-1": {
        "name": "SHA-1",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "sha-256": {
        "name": "SHA-256",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "sha-384": {
        "name": "SHA-384",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "sha-512": {
        "name": "SHA-512",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "snefru": {
        "name": "Snefru",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "tiger-128": {
        "name": "Tiger-128",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "tiger-160": {
        "name": "Tiger-160",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "tiger-192": {
        "name": "Tiger-192",
        "supports_hmac": True,
        "supports_salt": False,
    },
    "whirlpool": {
        "name": "Whirlpool",
        "supports_hmac": True,
        "supports_salt": False,
    },
}


def _read_file_data(file_path, config):
    upload_dir = config.get("UPLOAD_DIR", "uploads")
    full_path = os.path.join(upload_dir, file_path)
    if os.path.exists(full_path):
        with open(full_path, "rb") as f:
            return f.read()
    basename_path = os.path.join(upload_dir, os.path.basename(file_path))
    if os.path.exists(basename_path):
        with open(basename_path, "rb") as f:
            return f.read()
    if os.path.exists(file_path):
        with open(file_path, "rb") as f:
            return f.read()
    return None


def _compute_adler32(data):
    return format(zlib.adler32(data) & 0xFFFFFFFF, "08x")


def _compute_crc32(data):
    return format(zlib.crc32(data) & 0xFFFFFFFF, "08x")


def _compute_crc32b(data):
    crc = zlib.crc32(data) & 0xFFFFFFFF
    b = struct.pack(">I", crc)
    return b.hex()


def _compute_md4(data, hmac_key=None):
    if hmac_key:
        key_bytes = hmac_key.encode("utf-8")
        block_size = 64
        if len(key_bytes) > block_size:
            h = CryptoMD4.new()
            h.update(key_bytes)
            key_bytes = h.digest()
        key_bytes = key_bytes.ljust(block_size, b"\x00")
        o_key_pad = bytes(k ^ 0x5C for k in key_bytes)
        i_key_pad = bytes(k ^ 0x36 for k in key_bytes)
        inner = CryptoMD4.new()
        inner.update(i_key_pad + data)
        outer = CryptoMD4.new()
        outer.update(o_key_pad + inner.digest())
        return outer.hexdigest()
    h = CryptoMD4.new()
    h.update(data)
    return h.hexdigest()


def _compute_md5(data, hmac_key=None):
    if hmac_key:
        return hmac.new(hmac_key.encode("utf-8"), data, hashlib.md5).hexdigest()
    return hashlib.md5(data).hexdigest()


def _compute_sha1(data, hmac_key=None):
    if hmac_key:
        return hmac.new(hmac_key.encode("utf-8"), data, hashlib.sha1).hexdigest()
    return hashlib.sha1(data).hexdigest()


def _compute_sha256(data, hmac_key=None):
    if hmac_key:
        return hmac.new(hmac_key.encode("utf-8"), data, hashlib.sha256).hexdigest()
    return hashlib.sha256(data).hexdigest()


def _compute_sha384(data, hmac_key=None):
    if hmac_key:
        return hmac.new(hmac_key.encode("utf-8"), data, hashlib.sha384).hexdigest()
    return hashlib.sha384(data).hexdigest()


def _compute_sha512(data, hmac_key=None):
    if hmac_key:
        return hmac.new(hmac_key.encode("utf-8"), data, hashlib.sha512).hexdigest()
    return hashlib.sha512(data).hexdigest()


def _compute_ripemd160(data, hmac_key=None):
    if hmac_key:
        return hmac.new(
            hmac_key.encode("utf-8"), data, lambda: hashlib.new("ripemd160")
        ).hexdigest()
    return hashlib.new("ripemd160", data).hexdigest()


def _compute_ripemd128(data, hmac_key=None):
    from converters._ripemd128 import ripemd128

    if hmac_key:
        return _hmac_custom(hmac_key, data, ripemd128, 64)
    return ripemd128(data)


def _compute_htpasswd(data, salt=None):
    text = data.decode("utf-8", errors="replace")
    return apr_md5_crypt.hash(text, salt=salt) if salt else apr_md5_crypt.hash(text)


def _compute_blowfish(data):
    if len(data) > 72:
        data = data[:72]
    salt = _bcrypt_lib.gensalt()
    return _bcrypt_lib.hashpw(data, salt).decode("utf-8")


def _compute_des(data, salt=None):
    text = data.decode("utf-8", errors="replace")
    if len(text) > 8:
        text = text[:8]
    return des_crypt.hash(text, salt=salt) if salt else des_crypt.hash(text)


def _compute_gost(data, hmac_key=None):
    from converters._gost import gost_hash

    if hmac_key:
        return _hmac_custom(hmac_key, data, gost_hash, 32)
    return gost_hash(data)


def _compute_haval128(data, hmac_key=None):
    from converters._haval import haval128

    if hmac_key:
        return _hmac_custom(hmac_key, data, haval128, 128)
    return haval128(data)


def _compute_snefru(data, hmac_key=None):
    from converters._snefru import snefru256

    if hmac_key:
        return _hmac_custom(hmac_key, data, snefru256, 64)
    return snefru256(data)


def _compute_tiger(data, bits=192, hmac_key=None):
    from converters._tiger import tiger_hash

    full = tiger_hash(data)

    if bits == 128:
        result = full[:32]
    elif bits == 160:
        result = full[:40]
    else:
        result = full

    if hmac_key:
        return _hmac_custom_tiger(hmac_key, data, bits)

    return result


def _hmac_custom_tiger(hmac_key, data, bits):
    from converters._tiger import tiger_hash

    block_size = 64
    key_bytes = hmac_key.encode("utf-8")
    if len(key_bytes) > block_size:
        key_bytes = bytes.fromhex(tiger_hash(key_bytes))
    key_bytes = key_bytes.ljust(block_size, b"\x00")

    o_key_pad = bytes(k ^ 0x5C for k in key_bytes)
    i_key_pad = bytes(k ^ 0x36 for k in key_bytes)

    inner = bytes.fromhex(tiger_hash(i_key_pad + data))
    full = tiger_hash(o_key_pad + inner)
    if bits == 128:
        return full[:32]
    elif bits == 160:
        return full[:40]
    return full


def _compute_whirlpool(data, hmac_key=None):
    from converters._whirlpool import whirlpool_hash

    if hmac_key:
        return _hmac_custom(hmac_key, data, whirlpool_hash, 64)
    return whirlpool_hash(data)


def _hmac_custom(hmac_key, data, hash_func, block_size):
    key_bytes = hmac_key.encode("utf-8")
    if len(key_bytes) > block_size:
        key_bytes = bytes.fromhex(hash_func(key_bytes))
    key_bytes = key_bytes.ljust(block_size, b"\x00")

    o_key_pad = bytes(k ^ 0x5C for k in key_bytes)
    i_key_pad = bytes(k ^ 0x36 for k in key_bytes)

    inner = bytes.fromhex(hash_func(i_key_pad + data))
    return hash_func(o_key_pad + inner)


def _compute_hash(hash_type, data, options=None):
    hmac_key = None
    salt = None
    if options:
        hmac_key = options.get("salt", "").strip() or None
        salt = options.get("salt", "").strip() or None

    ht = hash_type.lower()

    if ht == "adler32":
        return _compute_adler32(data)
    elif ht == "htpasswd-apache":
        return _compute_htpasswd(data, salt=salt)
    elif ht == "blowfish":
        return _compute_blowfish(data)
    elif ht == "crc-32":
        return _compute_crc32(data)
    elif ht == "crc-32b":
        return _compute_crc32b(data)
    elif ht == "des":
        return _compute_des(data, salt=salt)
    elif ht == "gost":
        return _compute_gost(data, hmac_key=hmac_key)
    elif ht == "haval-128":
        return _compute_haval128(data, hmac_key=hmac_key)
    elif ht == "md4":
        return _compute_md4(data, hmac_key=hmac_key)
    elif ht == "md5":
        return _compute_md5(data, hmac_key=hmac_key)
    elif ht == "ripemd-128":
        return _compute_ripemd128(data, hmac_key=hmac_key)
    elif ht == "ripemd-160":
        return _compute_ripemd160(data, hmac_key=hmac_key)
    elif ht == "sha-1":
        return _compute_sha1(data, hmac_key=hmac_key)
    elif ht == "sha-256":
        return _compute_sha256(data, hmac_key=hmac_key)
    elif ht == "sha-384":
        return _compute_sha384(data, hmac_key=hmac_key)
    elif ht == "sha-512":
        return _compute_sha512(data, hmac_key=hmac_key)
    elif ht == "snefru":
        return _compute_snefru(data, hmac_key=hmac_key)
    elif ht == "tiger-128":
        return _compute_tiger(data, bits=128, hmac_key=hmac_key)
    elif ht == "tiger-160":
        return _compute_tiger(data, bits=160, hmac_key=hmac_key)
    elif ht == "tiger-192":
        return _compute_tiger(data, bits=192, hmac_key=hmac_key)
    elif ht == "whirlpool":
        return _compute_whirlpool(data, hmac_key=hmac_key)
    else:
        return None


def convert(urls, hash_type, options, config):
    try:
        results = []
        text_input = ""
        if options and isinstance(options, dict):
            text_input = options.get("title", "").strip()

        def _get_all_hash_formats(hash_hex, label):
            """
            Convert a hex hash string to multiple formats
            
            Args:
                hash_hex: The hash as a hex string (e.g., "73cd59406884623d...")
                label: The label for the input (e.g., "Text input" or filename)
            
            Returns:
                List of formatted strings for each hash representation
            """
            # Remove any whitespace and ensure lowercase
            hash_hex = hash_hex.strip().lower()
            
            # Convert hex string to bytes
            try:
                hash_bytes = bytes.fromhex(hash_hex)
            except ValueError:
                # If it's not a valid hex string, return just the original
                return [f"{label}: {hash_hex}"]
            
            # Generate different representations
            hex_lower = hash_hex
            hex_upper = hash_hex.upper()
            
            # Hex with colons (grouped every 2 characters)
            hex_colons = ':'.join(hex_lower[i:i+2] for i in range(0, len(hex_lower), 2))
            
            # Base64
            import base64
            base64_str = base64.b64encode(hash_bytes).decode('ascii')
            
            # Return all formats with clear labels
            return [
                f"{label} (original): {hex_lower}",
                f"{label} (HEX uppercase): {hex_upper}",
                f"{label} (HEX with colons): {hex_colons}",
                f"{label} (Base64): {base64_str}"
            ]

        has_files = False
        for url_entry in urls:
            file_path = None
            if isinstance(url_entry, str):
                try:
                    parsed = json.loads(url_entry)
                    if isinstance(parsed, dict):
                        file_path = parsed.get("path", "")
                except (json.JSONDecodeError, TypeError):
                    file_path = url_entry

            elif isinstance(url_entry, dict):
                file_path = url_entry.get("path", "")

            if file_path and file_path != "empty":
                file_data = _read_file_data(file_path, config)
                if file_data:
                    has_files = True
                    file_name = os.path.basename(file_path)
                    hash_result = _compute_hash(hash_type, file_data, options)
                    if hash_result is None:
                        results.append(f"{file_name}: Unsupported hash type '{hash_type}'")
                    else:
                        # Add all formats for this file
                        all_formats = _get_all_hash_formats(hash_result, file_name)
                        results.extend(all_formats)

        if text_input:
            text_data = text_input.encode("utf-8")
            hash_result = _compute_hash(hash_type, text_data, options)
            if hash_result is None:
                results.append(f"Text input: Unsupported hash type '{hash_type}'")
            else:
                # Add all formats for text input
                all_formats = _get_all_hash_formats(hash_result, "Text input")
                results.extend(all_formats)

        if not results:
            return {
                "error": True,
                "message": "No input provided. Please upload a file or enter text.",
            }

        return {"error": False, "results2": results}

    except Exception as e:
        return {"error": True, "message": f"Hash generation failed: {str(e)}"}