"""
Log Upload Service
Handles uploading log files to the server every hour using a dedicated thread
"""

import asyncio
import threading
import time
import os
import sys
from pathlib import Path
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List
from ws_package.log.writer import LogWriter
from ws_package.log.thread_logger import get_thread_logger, ThreadLogManager
from ws_package.entities.user_entity import UserEntity
from ws_package.utils.s3 import S3sync, s3_detail_for_upload
from ws_package.exceptions.custom_exceptions import ScreenshotError, NetworkError, ValidationError

logger = LogWriter.file(__name__)

class LogUploadService:
    """
    Service for uploading log files to the server every hour
    """
    
    def __init__(self, user_entity: UserEntity, org_id: str, s3_logs_path: str = "logs/"):
        self.user_entity = user_entity
        self.org_id = org_id
        self.s3_logs_path = s3_logs_path
        self.upload_thread = None
        self.stop_event = threading.Event()
        self._stats_lock = threading.Lock()
        self.upload_interval = 3600  # 1 hour in seconds
        self.last_upload_time = None
        self.max_retries = 3
        self.retry_delay = 300  # 5 minutes between retries
        self.failed_files = {}  # Track failed files with retry counts
        self.upload_stats = {
            'total_uploads': 0,
            'successful_uploads': 0,
            'failed_uploads': 0,
            'retry_uploads': 0,
            'last_upload_time': None,
            'last_error': None,
            'failed_files_count': 0
        }
        
        # Thread logger for this service
        self.thread_logger = get_thread_logger("LogUploadThread", "LogUploadService")
        
        # Initialize the upload thread
        self._start_upload_thread()
    
    def _start_upload_thread(self):
        """Start the log upload thread"""
        self.upload_thread = threading.Thread(
            target=self._upload_worker,
            name="LogUploadThread",
            daemon=True
        )
        self.upload_thread.start()
        self.thread_logger.info("Log upload thread started", {
            'interval_seconds': self.upload_interval,
            'org_id': self.org_id,
            'user_id': self.user_entity.user_id
        })
    
    def _upload_worker(self):
        """Main worker thread for uploading logs"""
        while not self.stop_event.is_set():
            try:
                # Check if it's time to upload
                current_time = datetime.now()
                
                if (self.last_upload_time is None or 
                    (current_time - self.last_upload_time).total_seconds() >= self.upload_interval):
                    
                    self.thread_logger.info("Starting scheduled log upload", {
                        'current_time': current_time.isoformat(),
                        'last_upload_time': self.last_upload_time.isoformat() if self.last_upload_time else None
                    })
                    
                    # Perform the upload
                    self._upload_logs()
                    
                    # Update last upload time
                    self.last_upload_time = current_time
                    with self._stats_lock:
                        self.upload_stats['last_upload_time'] = current_time.isoformat()

                # Sleep for a short interval to check again
                time.sleep(60)  # Check every minute

            except Exception as e:
                self.thread_logger.error("Error in upload worker: %s", e, {'error': str(e)})
                with self._stats_lock:
                    self.upload_stats['last_error'] = str(e)
                time.sleep(60)  # Wait before retrying

    def _upload_logs(self):
        """Upload log files to the server"""
        try:
            with self._stats_lock:
                self.upload_stats['total_uploads'] += 1

            # Get log files to upload
            log_files = self._get_log_files_to_upload()

            if not log_files:
                self.thread_logger.info("No log files to upload")
                return

            self.thread_logger.info("Found %s log files to upload", len(log_files), {
                'file_count': len(log_files),
                'files': [str(f) for f in log_files]
            })

            # Upload each log file with retry logic
            for log_file in log_files:
                try:
                    self._upload_single_log_file_with_retry(log_file)
                    file_key = str(log_file)
                    with self._stats_lock:
                        self.upload_stats['successful_uploads'] += 1
                        if file_key in self.failed_files:
                            del self.failed_files[file_key]

                except Exception as e:
                    self.thread_logger.error("Failed to upload log file %s after retries: %s", log_file, e, {
                        'file': str(log_file),
                        'error': str(e)
                    })
                    file_key = str(log_file)
                    with self._stats_lock:
                        self.upload_stats['failed_uploads'] += 1
                        self.upload_stats['last_error'] = str(e)
                        self.failed_files.setdefault(file_key, 0)
                        self.failed_files[file_key] += 1

            with self._stats_lock:
                self.upload_stats['failed_files_count'] = len(self.failed_files)

        except Exception as e:
            self.thread_logger.error("Error in log upload process: %s", e, {'error': str(e)})
            with self._stats_lock:
                self.upload_stats['failed_uploads'] += 1
                self.upload_stats['last_error'] = str(e)
    
    def _get_log_files_to_upload(self) -> List[Path]:
        """Get list of log files that need to be uploaded"""
        log_files = []
        
        try:
            # Get log directory from ThreadLogManager
            log_manager = ThreadLogManager.get_instance()
            log_dir = log_manager.log_dir
            
            self.thread_logger.info("Scanning log directory: %s", log_dir)
            
            # Find log files that are older than 1 hour
            cutoff_time = datetime.now() - timedelta(hours=1)
            
            for log_file in log_dir.glob("*.log"):
                try:
                    # Check file modification time
                    file_mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
                    
                    if file_mtime < cutoff_time:
                        log_files.append(log_file)
                        self.thread_logger.info("Found log file to upload: %s", log_file, {
                            'file': str(log_file),
                            'modified_time': file_mtime.isoformat(),
                            'cutoff_time': cutoff_time.isoformat()
                        })
                    
                except Exception as e:
                    self.thread_logger.warning("Could not process log file %s: %s", log_file, e)
                    continue
            
            # Also check for JSON log files
            for json_file in log_dir.glob("*.json"):
                try:
                    file_mtime = datetime.fromtimestamp(json_file.stat().st_mtime)
                    
                    if file_mtime < cutoff_time:
                        log_files.append(json_file)
                        self.thread_logger.info("Found JSON log file to upload: %s", json_file, {
                            'file': str(json_file),
                            'modified_time': file_mtime.isoformat(),
                            'cutoff_time': cutoff_time.isoformat()
                        })
                    
                except Exception as e:
                    self.thread_logger.warning("Could not process JSON log file %s: %s", json_file, e)
                    continue
            
        except Exception as e:
            self.thread_logger.error("Error getting log files to upload: %s", e, {'error': str(e)})
        
        return log_files
    
    def _upload_single_log_file_with_retry(self, log_file: Path):
        """Upload a single log file to the server with retry logic"""
        file_key = str(log_file)
        retry_count = self.failed_files.get(file_key, 0)
        
        for attempt in range(self.max_retries + 1):
            try:
                self._upload_single_log_file(log_file)
                return  # Success, exit retry loop
                
            except Exception as e:
                if attempt < self.max_retries:
                    retry_count += 1
                    self.thread_logger.warning("Upload attempt %s failed for %s, retrying in %ss", attempt + 1, log_file, self.retry_delay, {
                        'file': str(log_file),
                        'attempt': attempt + 1,
                        'max_retries': self.max_retries,
                        'error': str(e)
                    })
                    
                    with self._stats_lock:
                        self.upload_stats['retry_uploads'] += 1
                    time.sleep(self.retry_delay)
                else:
                    # Final attempt failed
                    self.thread_logger.error("All upload attempts failed for %s", log_file, {
                        'file': str(log_file),
                        'total_attempts': attempt + 1,
                        'error': str(e)
                    })
                    raise e
    
    def _upload_single_log_file(self, log_file: Path):
        """Upload a single log file to the server"""
        try:
            # Create destination path for S3
            timestamp = datetime.fromtimestamp(log_file.stat().st_mtime)
            filename = log_file.name
            dest_path = f"{self.s3_logs_path}{self.org_id}/{self.user_entity.user_id}/{timestamp.strftime('%Y-%m-%d-%H-%M-%S')}-{filename}"
            
            self.thread_logger.info("Uploading log file to S3", {
                'source_file': str(log_file),
                'dest_path': dest_path,
                'file_size': log_file.stat().st_size
            })
            
            # Use the existing screenshot service sync method for upload
            # This reuses the same S3 upload logic
            asyncio.run(self._async_upload_file(str(log_file), dest_path))
            
            self.thread_logger.info("Successfully uploaded log file: %s", filename, {
                'file': str(log_file),
                'dest_path': dest_path
            })
            
        except Exception as e:
            self.thread_logger.error("Failed to upload log file %s: %s", log_file, e, {
                'file': str(log_file),
                'error': str(e)
            })
            raise
    
    async def _async_upload_file(self, src_path: str, dest_path: str):
        """Async wrapper for file upload"""
        try:
            # Get S3 details
            detail = await s3_detail_for_upload(self.org_id, self.user_entity)
            if not detail:
                raise ValidationError(f"Failed to get S3 configuration for org_id: {self.org_id}")
            
            s3_sync = S3sync(detail)
            
            # Upload based on bucket type
            if self.user_entity.bucket == "hetzner":
                self.thread_logger.info("Uploading to Hetzner bucket")
                result = await s3_sync.upload_file_to_hetzner(src_path, dest_path)
            else:
                self.thread_logger.info("Uploading to AWS bucket")
                result = await s3_sync.upload_file(src_path, dest_path)
            
            if not result:
                raise ScreenshotError(f"Upload failed - no result returned for {src_path}")
            
            self.thread_logger.info("Upload completed successfully", {
                'result_url': result,
                'dest_path': dest_path
            })
            
            return result
            
        except Exception as e:
            self.thread_logger.error("Error in async upload: %s", e, {'error': str(e)})
            if 'S3_TIME_TAMPERING' in str(e) or 'RequestTimeTooSkewed' in str(e):
                self.thread_logger.warning(
                    "Log upload skipped: S3 RequestTimeTooSkewed (clock skew, not timezone mismatch): %s",
                    e,
                )
            raise
    
    def force_upload_now(self):
        """Force an immediate log upload"""
        self.thread_logger.info("Force upload requested")
        self._upload_logs()
    
    def get_upload_stats(self) -> Dict[str, Any]:
        """Get upload statistics"""
        with self._stats_lock:
            total_uploads = self.upload_stats['total_uploads']
            return {
                'total_uploads': total_uploads,
                'successful_uploads': self.upload_stats['successful_uploads'],
                'failed_uploads': self.upload_stats['failed_uploads'],
                'retry_uploads': self.upload_stats['retry_uploads'],
                'failed_files_count': self.upload_stats['failed_files_count'],
                'success_rate': (self.upload_stats['successful_uploads'] / max(1, total_uploads)) * 100,
                'last_upload_time': self.upload_stats['last_upload_time'],
                'last_error': self.upload_stats['last_error'],
                'thread_alive': self.upload_thread.is_alive() if self.upload_thread else False,
                'upload_interval_seconds': self.upload_interval,
                'max_retries': self.max_retries,
                'retry_delay_seconds': self.retry_delay,
                'failed_files': dict(self.failed_files),
            }
    
    def stop(self):
        """Stop the log upload service"""
        self.thread_logger.info("Stopping log upload service")
        self.stop_event.set()
        
        if self.upload_thread and self.upload_thread.is_alive():
            self.upload_thread.join(timeout=5)
        
        self.thread_logger.info("Log upload service stopped")


class LogUploadManager:
    """
    Manager class for handling multiple log upload services
    """
    
    def __init__(self):
        self.upload_services: Dict[str, LogUploadService] = {}
        self.thread_logger = get_thread_logger("LogUploadManager", "LogUploadService")
    
    def add_upload_service(self, service_id: str, user_entity: UserEntity, org_id: str, s3_logs_path: str = "logs/"):
        """Add a new log upload service"""
        try:
            if service_id in self.upload_services:
                self.thread_logger.warning("Upload service %s already exists, replacing it", service_id)
                self.upload_services[service_id].stop()
            
            service = LogUploadService(user_entity, org_id, s3_logs_path)
            self.upload_services[service_id] = service
            
            self.thread_logger.info("Added log upload service: %s", service_id, {
                'service_id': service_id,
                'org_id': org_id,
                'user_id': user_entity.user_id
            })
            
        except Exception as e:
            self.thread_logger.error("Error adding upload service %s: %s", service_id, e, {'error': str(e)})
    
    def remove_upload_service(self, service_id: str):
        """Remove a log upload service"""
        try:
            if service_id in self.upload_services:
                self.upload_services[service_id].stop()
                del self.upload_services[service_id]
                self.thread_logger.info("Removed log upload service: %s", service_id)
            else:
                self.thread_logger.warning("Upload service %s not found", service_id)
                
        except Exception as e:
            self.thread_logger.error("Error removing upload service %s: %s", service_id, e, {'error': str(e)})
    
    def force_upload_all(self):
        """Force upload for all services"""
        self.thread_logger.info("Force upload requested for all services")
        for service_id, service in self.upload_services.items():
            try:
                service.force_upload_now()
                self.thread_logger.info("Force upload completed for service: %s", service_id)
            except Exception as e:
                self.thread_logger.error("Error in force upload for service %s: %s", service_id, e, {'error': str(e)})
    
    def get_all_stats(self) -> Dict[str, Any]:
        """Get statistics for all upload services"""
        stats = {}
        for service_id, service in self.upload_services.items():
            stats[service_id] = service.get_upload_stats()
        return stats
    
    def stop_all(self):
        """Stop all upload services"""
        self.thread_logger.info("Stopping all log upload services")
        for service_id, service in self.upload_services.items():
            try:
                service.stop()
                self.thread_logger.info("Stopped service: %s", service_id)
            except Exception as e:
                self.thread_logger.error("Error stopping service %s: %s", service_id, e, {'error': str(e)})
        
        self.upload_services.clear()


# Global instance
_log_upload_manager = None

def get_log_upload_manager() -> LogUploadManager:
    """Get the global log upload manager instance"""
    global _log_upload_manager
    if _log_upload_manager is None:
        _log_upload_manager = LogUploadManager()
    return _log_upload_manager

def add_log_upload_service(service_id: str, user_entity: UserEntity, org_id: str, s3_logs_path: str = "logs/"):
    """Add a log upload service"""
    manager = get_log_upload_manager()
    manager.add_upload_service(service_id, user_entity, org_id, s3_logs_path)

def remove_log_upload_service(service_id: str):
    """Remove a log upload service"""
    manager = get_log_upload_manager()
    manager.remove_upload_service(service_id)

def force_log_upload_all():
    """Force upload for all services"""
    manager = get_log_upload_manager()
    manager.force_upload_all()

def get_log_upload_stats():
    """Get statistics for all upload services"""
    manager = get_log_upload_manager()
    return manager.get_all_stats()

def stop_all_log_upload_services():
    """Stop all log upload services"""
    manager = get_log_upload_manager()
    manager.stop_all()
