"""
Chrome Downloads Manager: Monitors the Chrome downloads folder and automatically
uploads new files to S3 bucket for backup and processing.
"""

import os
import time
import threading
import logging
import hashlib
from pathlib import Path
from typing import Dict, List, Optional, Set
from datetime import datetime, timedelta

import boto3
from botocore.exceptions import ClientError, NoCredentialsError

from .constants import (
    CHROME_DOWNLOADS_CHECK_INTERVAL,
    CHROME_DOWNLOADS_FOLDER,
    CHROME_DOWNLOADS_S3_BUCKET,
    CHROME_DOWNLOADS_S3_PREFIX
)


class ChromeDownloadsManager:
    """Manages automatic upload of Chrome downloads to S3 bucket."""
    
    def __init__(self, app, *, poll_interval: float | None = None) -> None:
        self.app = app
        self.poll_interval = poll_interval or CHROME_DOWNLOADS_CHECK_INTERVAL
        self._stop = False
        self.s3 = None
        self.known_files: Set[str] = set()  # Track files we've already processed
        self.file_hashes: Dict[str, str] = {}  # Track file hashes to detect changes
        
        # Check if we're in local development mode
        self.is_local = bool(os.getenv("LOCAL_DEV", "").lower() in ("true", "1", "yes"))
        if self.is_local:
            logging.info("[ChromeDownloads] Running in local development mode - S3 operations disabled")
            self.s3 = None
            return

        # Initialize S3 client with error handling
        try:
            region = os.getenv("AWS_REGION", "ap-southeast-2")
            self.s3 = boto3.client("s3", region_name=region)
            # Test AWS credentials by listing a bucket
            try:
                self.s3.list_buckets()
                logging.info("[ChromeDownloads] AWS S3 client initialized successfully")
            except Exception as cred_test_error:
                logging.warning(f"[ChromeDownloads] AWS credentials may be invalid: {cred_test_error}")
        except Exception as init_error:
            logging.error(f"[ChromeDownloads] Failed to initialize S3 client: {init_error}")
            self.s3 = None

    def start(self) -> None:
        """Start the Chrome downloads watcher in a background thread."""
        threading.Thread(target=self._run, daemon=True).start()
        logging.info("[ChromeDownloads] Started Chrome downloads watcher (checking every 5 seconds)")

    def stop(self) -> None:
        """Stop the Chrome downloads watcher."""
        self._stop = True
        logging.info("[ChromeDownloads] Stopped Chrome downloads watcher")

    def _run(self) -> None:
        """Main monitoring loop - checks for new downloads every 5 seconds."""
        while not self._stop:
            try:
                self._check_downloads_folder()
                time.sleep(self.poll_interval)
            except Exception as e:
                logging.error(f"[ChromeDownloads] Error in monitoring loop: {e}")
                time.sleep(self.poll_interval)  # Continue monitoring even on error

    def _check_downloads_folder(self) -> None:
        """Check the downloads folder for new files and upload them to S3."""
        if not self.s3:
            logging.debug("[ChromeDownloads] S3 client not available, skipping check")
            return

        downloads_path = Path(CHROME_DOWNLOADS_FOLDER)
        if not downloads_path.exists():
            logging.warning(f"[ChromeDownloads] Downloads folder does not exist: {downloads_path}")
            return

        try:
            # Get all files in downloads folder
            current_files = set()
            for file_path in downloads_path.iterdir():
                if file_path.is_file():
                    current_files.add(str(file_path))
                    
                    # Check if this is a new file or if an existing file has changed
                    if self._should_upload_file(file_path):
                        self._upload_file_to_s3(file_path)
                        # Mark as processed
                        self.known_files.add(str(file_path))
                        self.file_hashes[str(file_path)] = self._calculate_file_hash(file_path)

            # Clean up tracking for files that no longer exist
            removed_files = self.known_files - current_files
            for removed_file in removed_files:
                self.known_files.discard(removed_file)
                self.file_hashes.pop(removed_file, None)

        except Exception as e:
            logging.error(f"[ChromeDownloads] Error checking downloads folder: {e}")

    def _should_upload_file(self, file_path: Path) -> bool:
        """Determine if a file should be uploaded to S3."""
        file_str = str(file_path)
        
        # Skip if we've already processed this file
        if file_str in self.known_files:
            # Check if file has changed (size or modification time)
            current_hash = self._calculate_file_hash(file_path)
            if current_hash == self.file_hashes.get(file_str):
                return False  # File hasn't changed
        
        # Skip temporary files and system files
        if self._is_temporary_file(file_path):
            return False
            
        # Skip files that are still being downloaded (check if file is growing)
        if self._is_file_downloading(file_path):
            return False
            
        return True

    def _is_temporary_file(self, file_path: Path) -> bool:
        """Check if a file is a temporary download file."""
        filename = file_path.name.lower()
        
        # Skip common temporary file patterns
        temp_patterns = [
            '.tmp', '.temp', '.crdownload', '.part', '.download',
            '~', '.swp', '.bak', '.old'
        ]
        
        for pattern in temp_patterns:
            if pattern in filename:
                return True
                
        return False

    def _is_file_downloading(self, file_path: Path) -> bool:
        """Check if a file is currently being downloaded."""
        try:
            # Get file size and modification time
            stat = file_path.stat()
            current_size = stat.st_size
            current_mtime = stat.st_mtime
            
            # Wait a bit and check again
            time.sleep(0.5)  # Reduced wait time for faster 5-second checks
            
            # Get updated stats
            new_stat = file_path.stat()
            new_size = new_stat.st_size
            new_mtime = new_stat.st_mtime
            
            # If file size or modification time changed, it's still downloading
            if current_size != new_size or current_mtime != new_mtime:
                return True
                
        except Exception:
            # If we can't check, assume it's not downloading
            pass
            
        return False

    def _calculate_file_hash(self, file_path: Path) -> str:
        """Calculate a simple hash of file size and modification time."""
        try:
            stat = file_path.stat()
            # Use size + modification time as a simple hash
            hash_input = f"{stat.st_size}_{stat.st_mtime}"
            return hashlib.md5(hash_input.encode()).hexdigest()
        except Exception:
            return ""

    def _upload_file_to_s3(self, file_path: Path) -> None:
        """Upload a file to S3 bucket."""
        if not self.s3:
            logging.warning("[ChromeDownloads] S3 client not available, cannot upload file")
            return

        try:
            # Create S3 key with timestamp and original filename
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = file_path.name
            s3_key = f"{CHROME_DOWNLOADS_S3_PREFIX}{timestamp}_{filename}"
            
            # Upload file to S3
            self.s3.upload_file(
                str(file_path),
                CHROME_DOWNLOADS_S3_BUCKET,
                s3_key,
                ExtraArgs={
                    'Metadata': {
                        'original_path': str(file_path),
                        'upload_timestamp': timestamp,
                        'file_size': str(file_path.stat().st_size),
                        'source': 'chrome_downloads_manager'
                    }
                }
            )
            
            logging.info(f"[ChromeDownloads] Successfully uploaded {filename} to s3://{CHROME_DOWNLOADS_S3_BUCKET}/{s3_key}")
            
            # Log to UI if available
            self._log_upload_to_ui(filename, s3_key)
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            if error_code == 'NoSuchBucket':
                logging.error(f"[ChromeDownloads] S3 bucket {CHROME_DOWNLOADS_S3_BUCKET} does not exist")
            elif error_code == 'AccessDenied':
                logging.error(f"[ChromeDownloads] Access denied to S3 bucket {CHROME_DOWNLOADS_S3_BUCKET}")
            else:
                logging.error(f"[ChromeDownloads] S3 upload error: {e}")
        except NoCredentialsError:
            logging.error("[ChromeDownloads] AWS credentials not found")
        except Exception as e:
            logging.error(f"[ChromeDownloads] Failed to upload {file_path.name}: {e}")

    def _log_upload_to_ui(self, filename: str, s3_key: str) -> None:
        """Log upload information to the UI if available."""
        if not hasattr(self.app, 'live_clicks_text') or not self.app.live_clicks_text:
            return
            
        try:
            self.app.live_clicks_text.config(state='normal')
            self.app.live_clicks_text.insert('end', f'Chrome Download Uploaded: {filename}\n')
            self.app.live_clicks_text.insert('end', f'S3 Location: s3://{CHROME_DOWNLOADS_S3_BUCKET}/{s3_key}\n')
            self.app.live_clicks_text.insert('end', f'Time: {datetime.now().strftime("%H:%M:%S")}\n')
            self.app.live_clicks_text.insert('end', '-' * 40 + '\n')
            self.app.live_clicks_text.see('end')
        except Exception:
            pass
        finally:
            try:
                self.app.live_clicks_text.config(state='disabled')
            except Exception:
                pass

    def get_upload_stats(self) -> Dict[str, any]:
        """Get statistics about uploaded files."""
        return {
            'total_files_processed': len(self.known_files),
            's3_bucket': CHROME_DOWNLOADS_S3_BUCKET,
            's3_prefix': CHROME_DOWNLOADS_S3_PREFIX,
            'downloads_folder': CHROME_DOWNLOADS_FOLDER,
            'is_monitoring': not self._stop,
            's3_available': bool(self.s3),
            'check_interval': self.poll_interval
        }
