"""
Instagram Perceptual Duplicate Detector

Detects visually similar Instagram content (even with text overlays, stickers, etc.)
and keeps the cleanest + highest quality version.

Priority: Clean (no overlays) > Quality (resolution/size)
"""

import os
import gc
import json
import uuid
from pathlib import Path
from typing import Optional, Dict, Tuple, List, TYPE_CHECKING
from datetime import datetime
from modules.universal_logger import get_logger

if TYPE_CHECKING:
    import numpy as np

try:
    import cv2
    import numpy as np
    OPENCV_AVAILABLE = True
except ImportError:
    OPENCV_AVAILABLE = False
    np = None  # Define np as None when not available

try:
    import imagehash
    from PIL import Image
    IMAGEHASH_AVAILABLE = True
except ImportError:
    IMAGEHASH_AVAILABLE = False

# OCR disabled — not currently needed
EASYOCR_AVAILABLE = False
TESSERACT_AVAILABLE = False


class InstagramPerceptualDuplicateDetector:
    """
    Detects perceptual duplicates in Instagram content and keeps cleanest + best quality
    """

    def __init__(self, unified_db, log_callback=None):
        """
        Initialize detector

        Args:
            unified_db: UnifiedDatabase instance
            log_callback: Optional legacy callback (deprecated, uses universal logger)
        """
        self.db = unified_db
        self.logger = get_logger('Perceptual_Duplicate_Detector')
        self.easyocr_reader = None

        # Initialize EasyOCR reader (lazy loading - only when needed)
        if EASYOCR_AVAILABLE:
            try:
                # Suppress PyTorch pin_memory warning (we're using CPU anyway)
                import warnings
                warnings.filterwarnings('ignore', category=UserWarning, module='torch.utils.data.dataloader')

                self.easyocr_reader = easyocr.Reader(['en'], gpu=False, verbose=False)
                self.logger.debug("EasyOCR initialized for text overlay detection", module="Perceptual")
            except Exception as e:
                self.logger.warning(f"Failed to initialize EasyOCR: {e}, will use Tesseract fallback", module="Perceptual")
                self.easyocr_reader = None

        # Check dependencies
        if not OPENCV_AVAILABLE:
            self.logger.warning("OpenCV not available - perceptual duplicate detection disabled", module="Perceptual")
        if not IMAGEHASH_AVAILABLE:
            self.logger.warning("imagehash not available - perceptual duplicate detection disabled", module="Perceptual")
        if not EASYOCR_AVAILABLE and not TESSERACT_AVAILABLE:
            self.logger.debug("No OCR available (EasyOCR or pytesseract) - text overlay detection disabled", module="Perceptual")

        self.dependencies_available = OPENCV_AVAILABLE and IMAGEHASH_AVAILABLE

    def check_and_handle_duplicate(self, file_path: str, platform: str, source: str, content_type: str = None) -> Optional[str]:
        """
        Check if file is a perceptual duplicate and handle accordingly

        ALWAYS records perceptual hash (even when disabled) to build historical database.
        Only performs duplicate detection/handling when enabled.

        Returns:
            - None if not a duplicate or feature disabled
            - "skip" if this file should be skipped (lower quality duplicate)
            - file_path if this file should be kept (same or better)
        """
        filename = Path(file_path).name

        self.logger.debug(f"[PERCEPTUAL] ENTRY: check_and_handle_duplicate called", module="Perceptual")
        self.logger.debug(f"[PERCEPTUAL]   File: {filename}", module="Perceptual")
        self.logger.debug(f"[PERCEPTUAL]   Platform: {platform}", module="Perceptual")
        self.logger.debug(f"[PERCEPTUAL]   Source: {source}", module="Perceptual")
        self.logger.debug(f"[PERCEPTUAL]   Content Type: {content_type}", module="Perceptual")

        if not self.dependencies_available:
            self.logger.warning(f"[PERCEPTUAL] SKIP: Dependencies not available (OpenCV/ImageHash missing)", module="Perceptual")
            return None

        # Get settings
        settings = self._get_settings()
        detection_enabled = settings.get('enabled', False)

        self.logger.debug(f"[PERCEPTUAL] Settings loaded:", module="Perceptual")
        self.logger.debug(f"[PERCEPTUAL]   Enabled: {detection_enabled}", module="Perceptual")
        self.logger.debug(f"[PERCEPTUAL]   Platforms: {settings.get('platforms', [])}", module="Perceptual")
        self.logger.debug(f"[PERCEPTUAL]   Threshold: {settings.get('perceptual_hash_threshold', 12)}", module="Perceptual")

        try:
            # ALWAYS calculate perceptual hash and scores (even when detection disabled)
            # This builds the historical database for future use
            self.logger.debug(f"[PERCEPTUAL] Calculating perceptual hash for {filename}...", module="Perceptual")
            phash = self._calculate_perceptual_hash(file_path)
            if not phash:
                self.logger.error(f"[PERCEPTUAL] FAILED: Could not calculate perceptual hash for {filename}", module="Perceptual")
                return None

            self.logger.debug(f"[PERCEPTUAL] Hash calculated: {phash[:32]}...", module="Perceptual")

            text_count, text_chars = self._detect_text_overlays(file_path) if settings.get('text_detection_enabled', True) else (0, 0)
            quality_metrics = self._get_quality_metrics(file_path)

            clean_score = self._calculate_clean_score(text_count, text_chars)
            quality_score = self._calculate_quality_score(quality_metrics)

            self.logger.debug(f"[PERCEPTUAL] Scores calculated:", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL]   Clean Score: {clean_score:.2f}", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL]   Quality Score: {quality_score:.2f}", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL]   Text Overlays: {text_count} ({text_chars} chars)", module="Perceptual")

            # If detection is disabled, just store the hash and return (no duplicate checking)
            if not detection_enabled:
                self.logger.debug(f"[PERCEPTUAL] SKIP: Detection disabled - storing hash only for {filename}", module="Perceptual")
                self._store_perceptual_hash(
                    file_path, platform, source, content_type,
                    phash, text_count, text_chars, quality_score, clean_score, quality_metrics
                )
                return None  # Detection disabled, allow file to proceed

            # Check if this platform is enabled for detection
            platform_enabled = platform.lower() in [p.lower() for p in settings.get('platforms', ['instagram'])]
            self.logger.debug(f"[PERCEPTUAL] Platform check: {platform} enabled = {platform_enabled}", module="Perceptual")

            if not platform_enabled:
                self.logger.debug(f"[PERCEPTUAL] SKIP: Platform '{platform}' not in enabled list - storing hash only", module="Perceptual")
                self._store_perceptual_hash(
                    file_path, platform, source, content_type,
                    phash, text_count, text_chars, quality_score, clean_score, quality_metrics
                )
                return None  # Platform not enabled, allow file to proceed

            # Detection is enabled - perform duplicate checking
            self.logger.debug(f"[PERCEPTUAL] CHECKING FOR DUPLICATES: {filename}", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL]   Platform: {platform}, Source: {source}", module="Perceptual")

            self.logger.log(
                f"[PERCEPTUAL] New file: {Path(file_path).name} | "
                f"Hash: {phash[:16]}... | Clean: {clean_score:.2f} | Quality: {quality_score:.2f}",
                "info"
            )

            # Find perceptual duplicates in database
            threshold = settings.get('perceptual_hash_threshold', 12)
            self.logger.debug(f"[PERCEPTUAL] Searching for similar files (threshold: {threshold})...", module="Perceptual")

            similar_files = self._find_similar_files(
                phash,
                platform,
                source,
                threshold
            )

            self.logger.debug(f"[PERCEPTUAL] Similar files found: {len(similar_files)}", module="Perceptual")

            if similar_files:
                for i, sim in enumerate(similar_files, 1):
                    self.logger.debug(
                        f"[PERCEPTUAL]   #{i}: {sim['filename']} | "
                        f"Distance: {sim['hamming_distance']} | "
                        f"Clean: {sim['clean_score']:.2f} | "
                        f"Quality: {sim['quality_score']:.2f}",
                        module="Perceptual"
                    )

            if not similar_files:
                # No duplicates found - store this file's hash and continue
                self.logger.debug(f"[PERCEPTUAL] NO DUPLICATES FOUND - keeping {filename}", module="Perceptual")
                self._store_perceptual_hash(
                    file_path, platform, source, content_type,
                    phash, text_count, text_chars, quality_score, clean_score, quality_metrics
                )
                return file_path  # Keep this file

            # Found similar file(s) - compare and decide which to keep
            best_existing = self._get_best_existing_file(similar_files)

            self.logger.debug(f"[PERCEPTUAL] DUPLICATE DETECTED!", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL] Best existing file: {best_existing['filename']}", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL]   Clean: {best_existing['clean_score']:.2f}", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL]   Quality: {best_existing['quality_score']:.2f}", module="Perceptual")
            self.logger.debug(f"[PERCEPTUAL]   Path: {best_existing['file_path']}", module="Perceptual")

            # Compare new file vs best existing
            self.logger.debug(f"[PERCEPTUAL] Comparing new vs existing...", module="Perceptual")
            comparison = self._compare_files(
                new_clean=clean_score,
                new_quality=quality_score,
                existing_clean=best_existing['clean_score'],
                existing_quality=best_existing['quality_score'],
                settings=settings
            )

            self.logger.debug(f"[PERCEPTUAL] Comparison result: {comparison}", module="Perceptual")

            if comparison == "new_better":
                # New file is better - move existing to recycle, keep new
                self.logger.info(
                    f"[PERCEPTUAL] Replacing {best_existing['filename']} with cleaner version: {filename}",
                    module="Perceptual"
                )

                # Move existing to recycle bin
                self._move_to_recycle(
                    best_existing['file_path'],
                    reason='replaced_with_cleaner_duplicate',
                    new_file=file_path
                )

                # Update database - replace old entry with new
                self._replace_perceptual_hash_entry(
                    old_id=best_existing['id'],
                    new_file_path=file_path,
                    new_phash=phash,
                    new_text_count=text_count,
                    new_text_chars=text_chars,
                    new_quality_score=quality_score,
                    new_clean_score=clean_score,
                    new_quality_metrics=quality_metrics
                )

                return file_path  # Keep new file

            elif comparison == "existing_better":
                # Existing file is better - move new to recycle, keep existing
                self.logger.info(
                    f"[PERCEPTUAL] Skipping {filename} (duplicate of {best_existing['filename']})",
                    module="Perceptual"
                )

                # Move new file to recycle bin
                self._move_to_recycle(
                    file_path,
                    reason='duplicate_lower_quality_or_has_overlays',
                    kept_file=best_existing['file_path']
                )

                return "skip"  # Skip this file

            else:
                # Same quality - keep existing (default behavior)
                self.logger.info(
                    f"[PERCEPTUAL] Skipping {filename} (same quality as {best_existing['filename']})",
                    module="Perceptual"
                )

                self._move_to_recycle(
                    file_path,
                    reason='duplicate_same_quality',
                    kept_file=best_existing['file_path']
                )

                return "skip"

        except Exception as e:
            self.logger.error(f"[PERCEPTUAL] EXCEPTION: {e}", module="Perceptual")
            import traceback
            self.logger.error(f"[PERCEPTUAL] Traceback:\n{traceback.format_exc()}", module="Perceptual")
            return None

    def _get_settings(self) -> dict:
        """Get Instagram perceptual duplicate settings from database"""
        try:
            with self.db.get_connection() as conn:
                cursor = conn.cursor()
                cursor.execute("SELECT value FROM settings WHERE key = 'instagram_perceptual_duplicates'")
                result = cursor.fetchone()
                if result:
                    return json.loads(result[0])
        except Exception as e:
            self.logger.debug(f"Failed to get perceptual duplicate settings: {e}", module="Perceptual")

        return {'enabled': False}

    def _calculate_perceptual_hash(self, file_path: str) -> Optional[str]:
        """Calculate perceptual hash for image or video"""
        if not IMAGEHASH_AVAILABLE:
            return None

        frame = None
        frame_rgb = None
        pil_image = None

        try:
            # For videos, extract middle frame
            if file_path.lower().endswith(('.mp4', '.mov', '.avi', '.mkv')):
                frame = self._extract_video_frame(file_path)
                if frame is None:
                    return None

                # Convert frame to PIL Image
                frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                pil_image = Image.fromarray(frame_rgb)
            else:
                # For images, open directly
                pil_image = Image.open(file_path)

            # Calculate perceptual hash (dHash - difference hash)
            phash = str(imagehash.dhash(pil_image, hash_size=16))
            return phash

        except Exception as e:
            self.logger.debug(f"Failed to calculate perceptual hash: {e}", module="Perceptual")
            return None
        finally:
            # Clean up memory
            if pil_image is not None:
                pil_image.close()
                del pil_image
            if frame_rgb is not None:
                del frame_rgb
            if frame is not None:
                del frame
            gc.collect()

    def _extract_video_frame(self, video_path: str, position: float = 0.5) -> Optional['np.ndarray']:
        """Extract a frame from video at given position (0.0 to 1.0)"""
        if not OPENCV_AVAILABLE:
            return None

        try:
            cap = cv2.VideoCapture(video_path)
            if not cap.isOpened():
                return None

            # Get total frames and seek to middle
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            target_frame = int(total_frames * position)
            cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)

            ret, frame = cap.read()
            cap.release()

            return frame if ret else None

        except Exception as e:
            self.logger.debug(f"Failed to extract video frame: {e}", module="Perceptual")
            return None

    def _detect_text_overlays(self, file_path: str) -> Tuple[int, int]:
        """
        Detect text overlays in image/video using EasyOCR (primary) or Tesseract (fallback)

        Returns:
            (text_region_count, total_text_characters)
        """
        if not self.easyocr_reader and not TESSERACT_AVAILABLE:
            return (0, 0)

        if not OPENCV_AVAILABLE:
            return (0, 0)

        image = None
        gray = None

        try:
            text_regions = 0
            total_chars = 0

            # Load image or extract video frame
            if file_path.lower().endswith(('.mp4', '.mov', '.avi', '.mkv')):
                image = self._extract_video_frame(file_path)
                if image is None:
                    return (0, 0)
            else:
                image = cv2.imread(file_path)
                if image is None:
                    return (0, 0)

            # Try EasyOCR first (better for Instagram overlays)
            if self.easyocr_reader:
                try:
                    # EasyOCR works directly with image arrays
                    results = self.easyocr_reader.readtext(image)

                    # EasyOCR returns list of (bbox, text, confidence)
                    for bbox, text, conf in results:
                        if conf > 0.5:  # Only use detections with >50% confidence
                            text_stripped = text.strip()
                            if text_stripped:
                                text_regions += 1
                                total_chars += len(text_stripped)

                    if text_regions > 0:
                        self.logger.log(
                            f"[OVERLAY] EasyOCR detected {text_regions} text regions, {total_chars} chars in {Path(file_path).name}",
                            "debug"
                        )
                        return (text_regions, total_chars)

                except Exception as e:
                    self.logger.debug(f"EasyOCR failed: {e}, falling back to Tesseract", module="Perceptual")

            # Fallback to Tesseract if EasyOCR didn't find anything or failed
            if TESSERACT_AVAILABLE:
                try:
                    # Convert to grayscale for Tesseract
                    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

                    # Run OCR with detailed data
                    ocr_data = pytesseract.image_to_data(
                        gray,
                        output_type=pytesseract.Output.DICT,
                        config='--psm 11'  # Sparse text mode
                    )

                    # Count text regions and characters
                    text_regions = 0
                    total_chars = 0
                    confidence_threshold = 30

                    for i, conf in enumerate(ocr_data['conf']):
                        if int(conf) > confidence_threshold:
                            text = ocr_data['text'][i].strip()
                            if text:
                                text_regions += 1
                                total_chars += len(text)

                    self.logger.log(
                        f"[OVERLAY] Tesseract (fallback) detected {text_regions} text regions, {total_chars} chars in {Path(file_path).name}",
                        "debug"
                    )

                except Exception as e:
                    self.logger.debug(f"Tesseract OCR failed: {e}", module="Perceptual")

            return (text_regions, total_chars)

        except Exception as e:
            self.logger.debug(f"Text overlay detection failed: {e}", module="Perceptual")
            return (0, 0)
        finally:
            # Clean up memory - these are large numpy arrays
            if gray is not None:
                del gray
            if image is not None:
                del image
            gc.collect()

    def _get_quality_metrics(self, file_path: str) -> dict:
        """Get quality metrics for file"""
        import subprocess

        metrics = {
            'resolution': 0,
            'width': 0,
            'height': 0,
            'file_size': 0,
            'bitrate': 0
        }

        try:
            # Get file size
            metrics['file_size'] = Path(file_path).stat().st_size

            # Use ffprobe for video metadata
            cmd = [
                'ffprobe',
                '-v', 'quiet',
                '-print_format', 'json',
                '-show_format',
                '-show_streams',
                file_path
            ]

            result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
            if result.returncode == 0:
                data = json.loads(result.stdout)

                # Get video stream
                video_stream = next((s for s in data.get('streams', []) if s.get('codec_type') == 'video'), None)
                if video_stream:
                    metrics['width'] = int(video_stream.get('width', 0))
                    metrics['height'] = int(video_stream.get('height', 0))
                    metrics['resolution'] = metrics['width'] * metrics['height']

                # Get bitrate
                format_info = data.get('format', {})
                if 'bit_rate' in format_info:
                    metrics['bitrate'] = int(format_info['bit_rate']) // 1000

        except Exception as e:
            self.logger.debug(f"Failed to get quality metrics: {e}", module="Perceptual")

        return metrics

    def _calculate_clean_score(self, text_count: int, text_chars: int) -> float:
        """
        Calculate cleanliness score (0-100)

        Higher score = cleaner (less text/overlays)
        """
        # Base score starts at 100 (perfectly clean)
        score = 100.0

        # Penalize for text regions (each region -10 points, max -50)
        text_penalty = min(text_count * 10, 50)
        score -= text_penalty

        # Penalize for character count (each 10 chars -5 points, max -40)
        char_penalty = min((text_chars // 10) * 5, 40)
        score -= char_penalty

        # Ensure score is between 0-100
        return max(0.0, min(100.0, score))

    def _calculate_quality_score(self, metrics: dict) -> float:
        """
        Calculate quality score (0-100)

        Based on resolution and file size
        """
        score = 0.0

        # Resolution score (0-60 points)
        # 1080p = 2,073,600 pixels = 60 points
        # 720p = 921,600 pixels = 27 points
        resolution = metrics.get('resolution', 0)
        if resolution > 0:
            resolution_score = min((resolution / 2_073_600) * 60, 60)
            score += resolution_score

        # File size score (0-40 points)
        # 10MB = 40 points
        # 5MB = 20 points
        file_size = metrics.get('file_size', 0)
        if file_size > 0:
            size_mb = file_size / (1024 * 1024)
            size_score = min((size_mb / 10) * 40, 40)
            score += size_score

        return min(100.0, score)

    def _find_similar_files(self, phash: str, platform: str, source: str, threshold: int) -> List[dict]:
        """Find files with similar perceptual hash"""
        try:
            with self.db.get_connection() as conn:
                cursor = conn.cursor()

                # Get all Instagram files (all methods now use platform='instagram')
                self.logger.debug(f"[PERCEPTUAL_SEARCH] Querying database:", module="Perceptual")
                self.logger.debug(f"[PERCEPTUAL_SEARCH]   Platform: instagram", module="Perceptual")
                self.logger.debug(f"[PERCEPTUAL_SEARCH]   Source: {source}", module="Perceptual")
                self.logger.debug(f"[PERCEPTUAL_SEARCH]   Threshold: {threshold}", module="Perceptual")

                # Search all Instagram content (regardless of method)
                # This catches reposts/duplicates from different accounts
                cursor.execute("""
                    SELECT id, file_path, filename, perceptual_hash,
                           text_overlay_count, text_overlay_chars,
                           quality_score, clean_score, resolution, file_size
                    FROM instagram_perceptual_hashes
                    WHERE platform = 'instagram'
                """)

                all_rows = cursor.fetchall()
                self.logger.debug(f"[PERCEPTUAL_SEARCH] Database returned {len(all_rows)} existing files (checking across all sources)", module="Perceptual")

                results = []
                checked_count = 0
                within_threshold = 0
                missing_files = 0

                for row in all_rows:
                    existing_hash = row[3]
                    existing_filename = row[2]

                    # Calculate Hamming distance
                    distance = self._hamming_distance(phash, existing_hash)
                    checked_count += 1

                    if distance <= threshold:
                        within_threshold += 1
                        # Check if file still exists
                        if Path(row[1]).exists():
                            self.logger.debug(
                                f"[PERCEPTUAL_SEARCH]   MATCH: {existing_filename} (distance: {distance})",
                                module="Perceptual"
                            )
                            results.append({
                                'id': row[0],
                                'file_path': row[1],
                                'filename': row[2],
                                'perceptual_hash': row[3],
                                'text_overlay_count': row[4],
                                'text_overlay_chars': row[5],
                                'quality_score': row[6],
                                'clean_score': row[7],
                                'resolution': row[8],
                                'file_size': row[9],
                                'hamming_distance': distance
                            })
                        else:
                            missing_files += 1
                            self.logger.debug(
                                f"[PERCEPTUAL_SEARCH]   MATCH but file missing: {existing_filename} (distance: {distance})",
                                module="Perceptual"
                            )

                self.logger.debug(
                    f"[PERCEPTUAL_SEARCH] Checked {checked_count} hashes, "
                    f"{within_threshold} within threshold, "
                    f"{missing_files} missing files, "
                    f"{len(results)} valid matches",
                    module="Perceptual"
                )

                return results

        except Exception as e:
            self.logger.error(f"Failed to find similar files: {e}", module="Perceptual")
            return []

    def _hamming_distance(self, hash1: str, hash2: str) -> int:
        """Calculate Hamming distance between two hashes"""
        if len(hash1) != len(hash2):
            return 999  # Invalid comparison

        return sum(c1 != c2 for c1, c2 in zip(hash1, hash2))

    def _get_best_existing_file(self, similar_files: List[dict]) -> dict:
        """Get the best existing file from similar files (highest clean + quality score)"""
        if not similar_files:
            return None

        # Sort by clean score (primary), then quality score (secondary)
        sorted_files = sorted(
            similar_files,
            key=lambda f: (f['clean_score'], f['quality_score']),
            reverse=True
        )

        return sorted_files[0]

    def _compare_files(self, new_clean: float, new_quality: float,
                      existing_clean: float, existing_quality: float,
                      settings: dict) -> str:
        """
        Compare new file vs existing file

        Returns: "new_better", "existing_better", or "same"
        """
        clean_weight = settings.get('clean_score_weight', 3)
        quality_weight = settings.get('quality_score_weight', 1)
        min_difference = settings.get('min_text_difference', 5)

        # IMPORTANT: Check for extreme quality differences first
        # If one file has significantly higher quality, prefer it unless clean score is terrible
        # This prevents low-resolution files from winning just because they have less detected text
        min_acceptable_clean = settings.get('min_acceptable_clean', 30)
        quality_ratio_threshold = settings.get('quality_ratio_threshold', 2.0)

        # Check if new file has dramatically better quality
        if new_quality > 0 and existing_quality > 0:
            quality_ratio = new_quality / existing_quality
            reverse_ratio = existing_quality / new_quality

            # New file has 2x+ better quality and acceptable clean score
            if quality_ratio >= quality_ratio_threshold and new_clean >= min_acceptable_clean:
                self.logger.debug(
                    f"[PERCEPTUAL] New file wins: {quality_ratio:.1f}x better quality "
                    f"(new: Q={new_quality:.1f}/C={new_clean:.1f}, existing: Q={existing_quality:.1f}/C={existing_clean:.1f})",
                    module="Perceptual"
                )
                return "new_better"

            # Existing file has 2x+ better quality and acceptable clean score
            if reverse_ratio >= quality_ratio_threshold and existing_clean >= min_acceptable_clean:
                self.logger.debug(
                    f"[PERCEPTUAL] Existing file wins: {reverse_ratio:.1f}x better quality "
                    f"(existing: Q={existing_quality:.1f}/C={existing_clean:.1f}, new: Q={new_quality:.1f}/C={new_clean:.1f})",
                    module="Perceptual"
                )
                return "existing_better"

        # Standard weighted comparison for cases without extreme quality differences
        new_score = (new_clean * clean_weight) + (new_quality * quality_weight)
        existing_score = (existing_clean * clean_weight) + (existing_quality * quality_weight)

        # Check if difference is significant
        score_diff = abs(new_score - existing_score)
        min_score_diff = min_difference * clean_weight  # Scale by weight

        if new_score > existing_score and score_diff >= min_score_diff:
            return "new_better"
        elif existing_score > new_score and score_diff >= min_score_diff:
            return "existing_better"
        else:
            return "same"

    def _store_perceptual_hash(self, file_path: str, platform: str, source: str, content_type: str,
                               phash: str, text_count: int, text_chars: int,
                               quality_score: float, clean_score: float, quality_metrics: dict):
        """Store perceptual hash and metadata in database (or update if exists)"""
        try:
            with self.db.get_connection() as conn:
                cursor = conn.cursor()

                # Check if hash already exists for this file
                cursor.execute("""
                    SELECT id FROM instagram_perceptual_hashes
                    WHERE file_path = ?
                """, (str(file_path),))

                existing = cursor.fetchone()

                if existing:
                    # Update existing entry
                    cursor.execute("""
                        UPDATE instagram_perceptual_hashes
                        SET filename = ?,
                            platform = ?,
                            source = ?,
                            content_type = ?,
                            perceptual_hash = ?,
                            text_overlay_count = ?,
                            text_overlay_chars = ?,
                            quality_score = ?,
                            clean_score = ?,
                            resolution = ?,
                            file_size = ?,
                            width = ?,
                            height = ?,
                            created_at = CURRENT_TIMESTAMP
                        WHERE id = ?
                    """, (
                        Path(file_path).name,
                        platform,
                        source,
                        content_type or 'unknown',
                        phash,
                        text_count,
                        text_chars,
                        quality_score,
                        clean_score,
                        quality_metrics.get('resolution', 0),
                        quality_metrics.get('file_size', 0),
                        quality_metrics.get('width', 0),
                        quality_metrics.get('height', 0),
                        existing[0]
                    ))
                    self.logger.debug(f"[PERCEPTUAL] Updated hash for {Path(file_path).name}", module="Perceptual")
                else:
                    # Insert new entry
                    entry_id = str(uuid.uuid4())

                    cursor.execute("""
                        INSERT INTO instagram_perceptual_hashes
                        (id, file_path, filename, platform, source, content_type,
                         perceptual_hash, text_overlay_count, text_overlay_chars,
                         quality_score, clean_score, resolution, file_size, width, height)
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """, (
                        entry_id,
                        str(file_path),
                        Path(file_path).name,
                        platform,
                        source,
                        content_type or 'unknown',
                        phash,
                        text_count,
                        text_chars,
                        quality_score,
                        clean_score,
                        quality_metrics.get('resolution', 0),
                        quality_metrics.get('file_size', 0),
                        quality_metrics.get('width', 0),
                        quality_metrics.get('height', 0)
                    ))
                    self.logger.debug(f"[PERCEPTUAL] Stored hash for {Path(file_path).name}", module="Perceptual")

                conn.commit()

        except Exception as e:
            self.logger.error(f"Failed to store perceptual hash: {e}", module="Perceptual")
            # Note: Connection context manager handles rollback automatically on exception

    def _replace_perceptual_hash_entry(self, old_id: str, new_file_path: str,
                                       new_phash: str, new_text_count: int, new_text_chars: int,
                                       new_quality_score: float, new_clean_score: float,
                                       new_quality_metrics: dict):
        """Replace old hash entry with new file data"""
        try:
            with self.db.get_connection() as conn:
                cursor = conn.cursor()

                cursor.execute("""
                    UPDATE instagram_perceptual_hashes
                    SET file_path = ?,
                        filename = ?,
                        perceptual_hash = ?,
                        text_overlay_count = ?,
                        text_overlay_chars = ?,
                        quality_score = ?,
                        clean_score = ?,
                        resolution = ?,
                        file_size = ?,
                        width = ?,
                        height = ?,
                        created_at = CURRENT_TIMESTAMP
                    WHERE id = ?
                """, (
                    str(new_file_path),
                    Path(new_file_path).name,
                    new_phash,
                    new_text_count,
                    new_text_chars,
                    new_quality_score,
                    new_clean_score,
                    new_quality_metrics.get('resolution', 0),
                    new_quality_metrics.get('file_size', 0),
                    new_quality_metrics.get('width', 0),
                    new_quality_metrics.get('height', 0),
                    old_id
                ))

                conn.commit()

        except Exception as e:
            self.logger.error(f"Failed to replace perceptual hash entry: {e}", module="Perceptual")

    def _move_to_recycle(self, file_path: str, reason: str, **metadata):
        """Move file to recycle bin"""
        try:
            self.db.move_to_recycle_bin(
                file_path=file_path,
                deleted_from='instagram_perceptual_duplicate_detection',
                deleted_by='system',
                metadata={
                    'reason': reason,
                    **metadata
                }
            )

            self.logger.debug(f"[PERCEPTUAL] Moved to recycle: {Path(file_path).name}", module="Perceptual")

        except Exception as e:
            self.logger.warning(f"Failed to move file to recycle: {e}", module="Perceptual")
            # Fallback to delete if recycle fails
            try:
                Path(file_path).unlink()
            except Exception:
                pass
