"""
Automated Backup System for Stream Processing Application

This module provides comprehensive backup functionality for database,
media files, and configuration data with rotation and cloud storage support.
"""

import os
import gzip
import shutil
import logging
from datetime import datetime, timedelta
from pathlib import Path
from typing import List, Optional
from django.conf import settings
from django.core.management import call_command
from django.core.files.storage import default_storage
from django.utils import timezone
from decouple import config
import subprocess
import json

logger = logging.getLogger(__name__)


class BackupManager:
    """
    Manages automated backups of database, media files, and configurations.
    
    Supports local and cloud storage with configurable retention policies.
    """
    
    def __init__(self):
        self.backup_dir = Path(config('BACKUP_DIR', default='/app/backups'))
        self.retention_days = config('BACKUP_RETENTION_DAYS', default=30, cast=int)
        self.compress_backups = config('COMPRESS_BACKUPS', default=True, cast=bool)
        self.cloud_storage_enabled = config('CLOUD_BACKUP_ENABLED', default=False, cast=bool)
        self.cloud_bucket = config('CLOUD_BACKUP_BUCKET', default='')
        
        # Ensure backup directory exists
        self.backup_dir.mkdir(parents=True, exist_ok=True)
        
        # Create subdirectories
        (self.backup_dir / 'database').mkdir(exist_ok=True)
        (self.backup_dir / 'media').mkdir(exist_ok=True)
        (self.backup_dir / 'config').mkdir(exist_ok=True)
    
    def create_database_backup(self) -> Optional[str]:
        """
        Create a PostgreSQL database backup using pg_dump.
        
        Returns:
            str: Path to the backup file if successful, None otherwise
        """
        try:
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            backup_filename = f"db_backup_{timestamp}.sql"
            backup_path = self.backup_dir / 'database' / backup_filename
            
            # Build pg_dump command
            db_config = settings.DATABASES['default']
            pg_dump_cmd = [
                'pg_dump',
                '--host', db_config['HOST'],
                '--port', str(db_config['PORT']),
                '--username', db_config['USER'],
                '--dbname', db_config['NAME'],
                '--no-password',
                '--verbose',
                '--clean',
                '--no-acl',
                '--no-owner',
                '--file', str(backup_path)
            ]
            
            # Set password via environment variable
            env = os.environ.copy()
            env['PGPASSWORD'] = db_config['PASSWORD']
            
            # Execute pg_dump
            result = subprocess.run(
                pg_dump_cmd,
                env=env,
                capture_output=True,
                text=True,
                timeout=3600  # 1 hour timeout
            )
            
            if result.returncode != 0:
                logger.error(f"Database backup failed: {result.stderr}")
                return None
            
            # Compress backup if enabled
            if self.compress_backups:
                compressed_path = f"{backup_path}.gz"
                with open(backup_path, 'rb') as f_in:
                    with gzip.open(compressed_path, 'wb') as f_out:
                        shutil.copyfileobj(f_in, f_out)
                
                # Remove uncompressed file
                backup_path.unlink()
                backup_path = Path(compressed_path)
            
            logger.info(f"Database backup created: {backup_path}")
            return str(backup_path)
            
        except Exception as e:
            logger.error(f"Database backup failed: {str(e)}")
            return None
    
    def create_media_backup(self) -> Optional[str]:
        """
        Create a backup of media files including stream segments and uploads.
        
        Returns:
            str: Path to the backup archive if successful, None otherwise
        """
        try:
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            backup_filename = f"media_backup_{timestamp}"
            backup_path = self.backup_dir / 'media' / backup_filename
            
            media_root = Path(settings.MEDIA_ROOT)
            if not media_root.exists():
                logger.warning("Media directory does not exist, skipping media backup")
                return None
            
            # Create tar archive
            archive_format = 'gztar' if self.compress_backups else 'tar'
            archive_path = shutil.make_archive(
                str(backup_path),
                archive_format,
                str(media_root.parent),
                str(media_root.name)
            )
            
            logger.info(f"Media backup created: {archive_path}")
            return archive_path
            
        except Exception as e:
            logger.error(f"Media backup failed: {str(e)}")
            return None
    
    def create_config_backup(self) -> Optional[str]:
        """
        Create a backup of configuration files and environment settings.
        
        Returns:
            str: Path to the backup file if successful, None otherwise
        """
        try:
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            backup_filename = f"config_backup_{timestamp}.json"
            backup_path = self.backup_dir / 'config' / backup_filename
            
            # Collect configuration data (excluding sensitive information)
            config_data = {
                'timestamp': timestamp,
                'django_settings': {
                    'DEBUG': settings.DEBUG,
                    'TIME_ZONE': settings.TIME_ZONE,
                    'LANGUAGE_CODE': settings.LANGUAGE_CODE,
                    'INSTALLED_APPS': settings.INSTALLED_APPS,
                    'MIDDLEWARE': settings.MIDDLEWARE,
                },
                'stream_config': getattr(settings, 'STREAM_CONFIG', {}),
                'video_config': getattr(settings, 'VIDEO_CONFIG', {}),
                'audio_config': getattr(settings, 'AUDIO_CONFIG', {}),
                'jingle_config': getattr(settings, 'JINGLE_CONFIG', {}),
                'rest_framework': {
                    'PAGE_SIZE': settings.REST_FRAMEWORK.get('PAGE_SIZE'),
                    'DEFAULT_THROTTLE_RATES': settings.REST_FRAMEWORK.get('DEFAULT_THROTTLE_RATES'),
                },
                'celery_beat_schedule': getattr(settings, 'CELERY_BEAT_SCHEDULE', {}),
            }
            
            # Write configuration backup
            with open(backup_path, 'w') as f:
                json.dump(config_data, f, indent=2, default=str)
            
            # Compress if enabled
            if self.compress_backups:
                compressed_path = f"{backup_path}.gz"
                with open(backup_path, 'rb') as f_in:
                    with gzip.open(compressed_path, 'wb') as f_out:
                        shutil.copyfileobj(f_in, f_out)
                
                backup_path.unlink()
                backup_path = Path(compressed_path)
            
            logger.info(f"Configuration backup created: {backup_path}")
            return str(backup_path)
            
        except Exception as e:
            logger.error(f"Configuration backup failed: {str(e)}")
            return None
    
    def create_full_backup(self) -> dict:
        """
        Create a complete backup including database, media, and configuration.
        
        Returns:
            dict: Backup results with file paths and status
        """
        backup_results = {
            'timestamp': datetime.now().isoformat(),
            'database': None,
            'media': None,
            'config': None,
            'success': False
        }
        
        try:
            # Create database backup
            db_backup = self.create_database_backup()
            if db_backup:
                backup_results['database'] = db_backup
            
            # Create media backup
            media_backup = self.create_media_backup()
            if media_backup:
                backup_results['media'] = media_backup
            
            # Create config backup
            config_backup = self.create_config_backup()
            if config_backup:
                backup_results['config'] = config_backup
            
            # Mark as successful if at least database backup succeeded
            backup_results['success'] = bool(db_backup)
            
            # Upload to cloud storage if enabled
            if self.cloud_storage_enabled and backup_results['success']:
                self.upload_to_cloud(backup_results)
            
            logger.info(f"Full backup completed: {backup_results}")
            return backup_results
            
        except Exception as e:
            logger.error(f"Full backup failed: {str(e)}")
            backup_results['error'] = str(e)
            return backup_results
    
    def upload_to_cloud(self, backup_results: dict) -> bool:
        """
        Upload backup files to cloud storage.
        
        Args:
            backup_results: Dictionary containing backup file paths
            
        Returns:
            bool: True if upload successful, False otherwise
        """
        try:
            # This is a placeholder for cloud storage integration
            # In production, implement with AWS S3, Google Cloud Storage, etc.
            logger.info("Cloud backup upload would be implemented here")
            return True
            
        except Exception as e:
            logger.error(f"Cloud upload failed: {str(e)}")
            return False
    
    def cleanup_old_backups(self) -> int:
        """
        Remove backup files older than retention period.
        
        Returns:
            int: Number of files cleaned up
        """
        cleaned_count = 0
        cutoff_date = datetime.now() - timedelta(days=self.retention_days)
        
        try:
            for backup_type in ['database', 'media', 'config']:
                backup_subdir = self.backup_dir / backup_type
                if not backup_subdir.exists():
                    continue
                
                for backup_file in backup_subdir.iterdir():
                    if backup_file.is_file():
                        file_mtime = datetime.fromtimestamp(backup_file.stat().st_mtime)
                        if file_mtime < cutoff_date:
                            backup_file.unlink()
                            cleaned_count += 1
                            logger.info(f"Removed old backup: {backup_file}")
            
            logger.info(f"Cleaned up {cleaned_count} old backup files")
            return cleaned_count
            
        except Exception as e:
            logger.error(f"Backup cleanup failed: {str(e)}")
            return cleaned_count
    
    def restore_database_backup(self, backup_path: str) -> bool:
        """
        Restore database from a backup file.
        
        Args:
            backup_path: Path to the backup file
            
        Returns:
            bool: True if restore successful, False otherwise
        """
        try:
            backup_file = Path(backup_path)
            if not backup_file.exists():
                logger.error(f"Backup file not found: {backup_path}")
                return False
            
            # Handle compressed backups
            if backup_path.endswith('.gz'):
                # Decompress first
                decompressed_path = backup_path.replace('.gz', '')
                with gzip.open(backup_path, 'rb') as f_in:
                    with open(decompressed_path, 'wb') as f_out:
                        shutil.copyfileobj(f_in, f_out)
                backup_path = decompressed_path
            
            # Build psql restore command
            db_config = settings.DATABASES['default']
            psql_cmd = [
                'psql',
                '--host', db_config['HOST'],
                '--port', str(db_config['PORT']),
                '--username', db_config['USER'],
                '--dbname', db_config['NAME'],
                '--file', backup_path
            ]
            
            # Set password via environment variable
            env = os.environ.copy()
            env['PGPASSWORD'] = db_config['PASSWORD']
            
            # Execute restore
            result = subprocess.run(
                psql_cmd,
                env=env,
                capture_output=True,
                text=True,
                timeout=3600  # 1 hour timeout
            )
            
            if result.returncode != 0:
                logger.error(f"Database restore failed: {result.stderr}")
                return False
            
            logger.info(f"Database restored from: {backup_path}")
            return True
            
        except Exception as e:
            logger.error(f"Database restore failed: {str(e)}")
            return False
    
    def get_backup_status(self) -> dict:
        """
        Get status and information about existing backups.
        
        Returns:
            dict: Backup status information
        """
        status = {
            'backup_dir': str(self.backup_dir),
            'retention_days': self.retention_days,
            'compress_backups': self.compress_backups,
            'cloud_storage_enabled': self.cloud_storage_enabled,
            'backups': {
                'database': [],
                'media': [],
                'config': []
            },
            'total_size': 0
        }
        
        try:
            for backup_type in ['database', 'media', 'config']:
                backup_subdir = self.backup_dir / backup_type
                if backup_subdir.exists():
                    for backup_file in backup_subdir.iterdir():
                        if backup_file.is_file():
                            file_stat = backup_file.stat()
                            file_info = {
                                'name': backup_file.name,
                                'path': str(backup_file),
                                'size': file_stat.st_size,
                                'created_at': datetime.fromtimestamp(file_stat.st_mtime).isoformat()
                            }
                            status['backups'][backup_type].append(file_info)
                            status['total_size'] += file_stat.st_size
            
            # Sort backups by creation time (newest first)
            for backup_type in status['backups']:
                status['backups'][backup_type].sort(
                    key=lambda x: x['created_at'], 
                    reverse=True
                )
            
            return status
            
        except Exception as e:
            logger.error(f"Failed to get backup status: {str(e)}")
            status['error'] = str(e)
            return status


def create_scheduled_backup():
    """
    Create a scheduled backup (called by Celery task).
    
    Returns:
        dict: Backup results
    """
    backup_manager = BackupManager()
    return backup_manager.create_full_backup()


def cleanup_old_backups():
    """
    Clean up old backup files (called by Celery task).
    
    Returns:
        int: Number of files cleaned up
    """
    backup_manager = BackupManager()
    return backup_manager.cleanup_old_backups()


def restore_from_backup(backup_path: str, backup_type: str = 'database'):
    """
    Restore from a specific backup file.
    
    Args:
        backup_path: Path to the backup file
        backup_type: Type of backup ('database', 'media', 'config')
        
    Returns:
        bool: True if restore successful, False otherwise
    """
    backup_manager = BackupManager()
    
    if backup_type == 'database':
        return backup_manager.restore_database_backup(backup_path)
    elif backup_type == 'media':
        # Implement media restore logic
        logger.warning("Media restore not yet implemented")
        return False
    elif backup_type == 'config':
        # Implement config restore logic
        logger.warning("Config restore not yet implemented")
        return False
    else:
        logger.error(f"Unknown backup type: {backup_type}")
        return False
