#!/usr/bin/env python3
"""
Standalone DCT crash diagnostic script.
Run this outside of Flask to isolate the issue.

Usage:
    python test_dct_crash.py /path/to/your/large_image.jpg
"""

import sys
import gc
import traceback
import io

print("=" * 60)
print("DCT CRASH DIAGNOSTIC TOOL")
print("=" * 60)

# Step 1: Check Python and library versions
print("\n[1] ENVIRONMENT INFO")
print(f"Python: {sys.version}")

try:
    import numpy as np
    print(f"NumPy: {np.__version__}")
except ImportError as e:
    print(f"NumPy: NOT INSTALLED - {e}")
    sys.exit(1)

try:
    import scipy
    print(f"SciPy: {scipy.__version__}")
except ImportError as e:
    print(f"SciPy: NOT INSTALLED - {e}")
    sys.exit(1)

try:
    from PIL import Image
    import PIL
    print(f"Pillow: {PIL.__version__}")
except ImportError as e:
    print(f"Pillow: NOT INSTALLED - {e}")
    sys.exit(1)

# Step 2: Check which DCT module we're using
print("\n[2] DCT MODULE CHECK")
try:
    from scipy.fft import dct, idct
    print("Using: scipy.fft (preferred)")
    DCT_MODULE = "scipy.fft"
except ImportError:
    try:
        from scipy.fftpack import dct, idct
        print("Using: scipy.fftpack (legacy)")
        DCT_MODULE = "scipy.fftpack"
    except ImportError:
        print("ERROR: No DCT module available!")
        sys.exit(1)

# Step 3: Test basic DCT on small array
print("\n[3] BASIC DCT TEST (8x8 block)")
try:
    test_block = np.random.rand(8, 8).astype(np.float64)
    
    # 1D DCT on rows
    result = dct(test_block[0, :], norm='ortho')
    print(f"  1D DCT: OK (output shape: {result.shape})")
    
    # 1D IDCT
    recovered = idct(result, norm='ortho')
    error = np.max(np.abs(test_block[0, :] - recovered))
    print(f"  1D IDCT: OK (roundtrip error: {error:.2e})")
    
    # 2D via separable
    temp = np.zeros_like(test_block)
    for i in range(8):
        temp[:, i] = dct(test_block[:, i], norm='ortho')
    result2d = np.zeros_like(temp)
    for i in range(8):
        result2d[i, :] = dct(temp[i, :], norm='ortho')
    print(f"  2D DCT: OK")
    
    gc.collect()
    print("  GC after basic test: OK")
    
except Exception as e:
    print(f"  FAILED: {e}")
    traceback.print_exc()

# Step 4: Test with larger arrays (stress test)
print("\n[4] STRESS TEST (many 8x8 blocks)")
try:
    NUM_BLOCKS = 10000
    print(f"  Processing {NUM_BLOCKS} blocks...")
    
    for i in range(NUM_BLOCKS):
        block = np.random.rand(8, 8).astype(np.float64)
        
        # Forward DCT
        temp = np.zeros_like(block)
        for j in range(8):
            temp[:, j] = dct(block[:, j], norm='ortho')
        result = np.zeros_like(temp)
        for j in range(8):
            result[j, :] = dct(temp[j, :], norm='ortho')
        
        # Inverse DCT
        temp2 = np.zeros_like(result)
        for j in range(8):
            temp2[j, :] = idct(result[j, :], norm='ortho')
        recovered = np.zeros_like(temp2)
        for j in range(8):
            recovered[:, j] = idct(temp2[:, j], norm='ortho')
        
        if i % 1000 == 0:
            gc.collect()
            print(f"    {i}/{NUM_BLOCKS} blocks processed...")
    
    gc.collect()
    print(f"  Stress test PASSED")
    
except Exception as e:
    print(f"  FAILED at block {i}: {e}")
    traceback.print_exc()

# Step 5: Test with actual image if provided
if len(sys.argv) > 1:
    image_path = sys.argv[1]
    print(f"\n[5] IMAGE TEST: {image_path}")
    
    try:
        with open(image_path, 'rb') as f:
            image_data = f.read()
        print(f"  File size: {len(image_data) / 1024 / 1024:.2f} MB")
        
        img = Image.open(io.BytesIO(image_data))
        width, height = img.size
        print(f"  Dimensions: {width}x{height}")
        print(f"  Format: {img.format}")
        print(f"  Mode: {img.mode}")
        
        # Convert to grayscale float array
        gray = img.convert('L')
        arr = np.array(gray, dtype=np.float64)
        img.close()
        gray.close()
        print(f"  Array shape: {arr.shape}")
        print(f"  Array dtype: {arr.dtype}")
        
        # Pad to block boundary
        BLOCK_SIZE = 8
        h, w = arr.shape
        new_h = ((h + BLOCK_SIZE - 1) // BLOCK_SIZE) * BLOCK_SIZE
        new_w = ((w + BLOCK_SIZE - 1) // BLOCK_SIZE) * BLOCK_SIZE
        
        if new_h != h or new_w != w:
            padded = np.zeros((new_h, new_w), dtype=np.float64)
            padded[:h, :w] = arr
            arr = padded
            print(f"  Padded to: {arr.shape}")
        
        blocks_y = arr.shape[0] // BLOCK_SIZE
        blocks_x = arr.shape[1] // BLOCK_SIZE
        total_blocks = blocks_y * blocks_x
        print(f"  Total 8x8 blocks: {total_blocks}")
        
        # Process ALL blocks
        print(f"  Processing all blocks with DCT...")
        
        processed = 0
        for by in range(blocks_y):
            for bx in range(blocks_x):
                y = by * BLOCK_SIZE
                x = bx * BLOCK_SIZE
                
                block = arr[y:y+BLOCK_SIZE, x:x+BLOCK_SIZE].copy()
                
                # Forward DCT
                temp = np.zeros((8, 8), dtype=np.float64)
                for i in range(8):
                    temp[:, i] = dct(block[:, i], norm='ortho')
                dct_block = np.zeros((8, 8), dtype=np.float64)
                for i in range(8):
                    dct_block[i, :] = dct(temp[i, :], norm='ortho')
                
                # Inverse DCT
                temp2 = np.zeros((8, 8), dtype=np.float64)
                for i in range(8):
                    temp2[i, :] = idct(dct_block[i, :], norm='ortho')
                recovered = np.zeros((8, 8), dtype=np.float64)
                for i in range(8):
                    recovered[:, i] = idct(temp2[:, i], norm='ortho')
                
                processed += 1
                
            # GC after each row of blocks
            if by % 50 == 0:
                gc.collect()
                print(f"    Row {by}/{blocks_y} ({processed}/{total_blocks} blocks)")
        
        gc.collect()
        print(f"  Image DCT test PASSED ({processed} blocks)")
        
    except Exception as e:
        print(f"  FAILED: {e}")
        traceback.print_exc()

else:
    print("\n[5] IMAGE TEST: Skipped (no image path provided)")
    print("    Usage: python test_dct_crash.py /path/to/image.jpg")

# Step 6: Final cleanup test
print("\n[6] FINAL CLEANUP TEST")
try:
    gc.collect()
    gc.collect()
    gc.collect()
    print("  Multiple GC cycles: OK")
except Exception as e:
    print(f"  FAILED: {e}")

print("\n" + "=" * 60)
print("If this script completes without 'free(): invalid size',")
print("the issue is likely in PIL/jpegio interaction, not scipy DCT.")
print("=" * 60)

# Keep process alive briefly to catch delayed crashes
import time
print("\nWaiting 2 seconds for delayed crashes...")
time.sleep(2)
print("Done - no crash detected!")
