"""
Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com

Example job classes for queue management.
"""

import json
import os
import random
import time
import asyncio

import requests

from mcp_proxy_adapter.integrations.queuemgr_integration import QueueJobBase
# Note: registry is imported inside run() method to avoid pickle issues with module references


class DataProcessingJob(QueueJobBase):
    """Example data processing job."""

    def run(self) -> None:
        """Execute data processing job."""
        self.logger.info(
            f"DataProcessingJob {self.job_id}: Starting data processing"
        )

        # Simulate processing
        data = self.mcp_params.get("data", {})
        operation = self.mcp_params.get("operation", "process")

        time.sleep(2)  # Simulate work

        result = {
            "job_id": self.job_id,
            "operation": operation,
            "processed_at": time.time(),
            "data_size": len(json.dumps(data)),
            "status": "completed",
        }

        self.set_mcp_result(result)


class FileOperationJob(QueueJobBase):
    """Example file operation job."""

    def run(self) -> None:
        """Execute file operation job."""
        self.logger.info(
            f"FileOperationJob {self.job_id}: Starting file operation"
        )

        file_path = self.mcp_params.get("file_path", "")
        operation = self.mcp_params.get("operation", "read")

        try:
            if operation == "read" and os.path.exists(file_path):
                with open(file_path, "r") as f:
                    content = f.read()

                result = {
                    "job_id": self.job_id,
                    "operation": operation,
                    "file_path": file_path,
                    "file_size": len(content),
                    "status": "completed",
                }
            else:
                result = {
                    "job_id": self.job_id,
                    "operation": operation,
                    "file_path": file_path,
                    "error": f"File not found or invalid operation: {operation}",
                    "status": "failed",
                }

            self.set_mcp_result(result, result["status"])

        except Exception as e:
            self.set_mcp_error(f"File operation failed: {str(e)}")


class ApiCallJob(QueueJobBase):
    """Example API call job."""

    def run(self) -> None:
        """Execute API call job."""
        self.logger.info(f"ApiCallJob {self.job_id}: Starting API call")

        url = self.mcp_params.get("url", "")
        method = self.mcp_params.get("method", "GET")
        headers = self.mcp_params.get("headers", {})
        timeout = self.mcp_params.get("timeout", 30)

        try:
            response = requests.request(
                method=method, url=url, headers=headers, timeout=timeout
            )

            result = {
                "job_id": self.job_id,
                "url": url,
                "method": method,
                "status_code": response.status_code,
                "response_size": len(response.content),
                "status": "completed",
            }

            self.set_mcp_result(result)

        except Exception as e:
            self.set_mcp_error(f"API call failed: {str(e)}")


class CustomJob(QueueJobBase):
    """Example custom job."""

    def run(self) -> None:
        """Execute custom job."""
        self.logger.info(f"CustomJob {self.job_id}: Starting custom job")

        # Custom job logic here
        time.sleep(1)  # Simulate work

        result = {
            "job_id": self.job_id,
            "custom_data": self.mcp_params.get("custom_data", {}),
            "status": "completed",
        }

        self.set_mcp_result(result)


class LongRunningJob(QueueJobBase):
    """Example long-running job with progress updates."""

    def run(self) -> None:
        """Execute long-running job with progress updates."""
        self.logger.info(
            f"LongRunningJob {self.job_id}: Starting long-running job"
        )

        duration = self.mcp_params.get("duration", 10)  # Default 10 seconds
        task_type = self.mcp_params.get("task_type", "data_processing")

        self.set_status("running")
        self.set_description(f"Processing {task_type} task...")

        # Simulate long-running work with progress updates
        for i in range(duration):
            # Update progress
            progress = int((i + 1) / duration * 100)
            self.set_progress(progress)
            self.set_description(
                f"Processing {task_type} task... {progress}% complete"
            )

            # Simulate work
            time.sleep(1)

            # Simulate occasional errors (5% chance)
            if random.random() < 0.05:
                self.set_mcp_error(f"Simulated error at {progress}%", "failed")
                return

        # Complete successfully
        result = {
            "job_id": self.job_id,
            "task_type": task_type,
            "duration": duration,
            "completed_at": time.time(),
            "status": "completed",
        }

        self.set_mcp_result(result)


class BatchProcessingJob(QueueJobBase):
    """Example batch processing job."""

    def run(self) -> None:
        """Execute batch processing job."""
        self.logger.info(
            f"BatchProcessingJob {self.job_id}: Starting batch processing"
        )

        batch_size = self.mcp_params.get("batch_size", 100)
        items = self.mcp_params.get("items", [])

        self.set_status("running")
        self.set_description(f"Processing batch of {len(items)} items...")

        processed_items = []

        for i, item in enumerate(items):
            # Update progress
            progress = int((i + 1) / len(items) * 100)
            self.set_progress(progress)
            self.set_description(
                f"Processing item {i+1}/{len(items)}... {progress}% complete"
            )

            # Simulate processing each item
            time.sleep(0.1)  # 100ms per item

            # Simulate processing result
            processed_item = {
                "original": item,
                "processed": f"processed_{item}",
                "timestamp": time.time(),
            }
            processed_items.append(processed_item)

            # Simulate occasional processing errors (2% chance)
            if random.random() < 0.02:
                self.set_mcp_error(
                    f"Processing failed at item {i+1}: {item}", "failed"
                )
                return

        # Complete successfully
        result = {
            "job_id": self.job_id,
            "batch_size": batch_size,
            "processed_count": len(processed_items),
            "processed_items": processed_items,
            "completed_at": time.time(),
            "status": "completed",
        }

        self.set_mcp_result(result)


class PeriodicLoggingJob(QueueJobBase):
    """Job that writes messages to stdout every minute for testing log retrieval."""

    def run(self) -> None:
        """Execute periodic logging job."""
        import sys
        from datetime import datetime

        self.logger.info(
            f"PeriodicLoggingJob {self.job_id}: Starting periodic logging"
        )

        # Get parameters
        duration_minutes = self.mcp_params.get("duration_minutes", 5)
        message_prefix = self.mcp_params.get("message_prefix", "Log message")
        interval_seconds = self.mcp_params.get("interval_seconds", 60)

        # Write initial message
        start_time = datetime.now()
        print(f"[{start_time.strftime('%Y-%m-%d %H:%M:%S')}] {message_prefix} - Job started", file=sys.stdout, flush=True)
        print(f"[{start_time.strftime('%Y-%m-%d %H:%M:%S')}] Job ID: {self.job_id}", file=sys.stdout, flush=True)
        print(f"[{start_time.strftime('%Y-%m-%d %H:%M:%S')}] Duration: {duration_minutes} minutes, Interval: {interval_seconds} seconds", file=sys.stdout, flush=True)

        # Write periodic messages
        message_count = 0
        end_time = start_time.timestamp() + (duration_minutes * 60)

        while time.time() < end_time:
            current_time = datetime.now()
            message_count += 1
            message = f"[{current_time.strftime('%Y-%m-%d %H:%M:%S')}] {message_prefix} #{message_count} - Job {self.job_id} is running"
            print(message, file=sys.stdout, flush=True)
            
            # Also write to stderr occasionally (every 3rd message)
            if message_count % 3 == 0:
                error_message = f"[{current_time.strftime('%Y-%m-%d %H:%M:%S')}] DEBUG: Message #{message_count} (stderr output)"
                print(error_message, file=sys.stderr, flush=True)

            # Update progress
            elapsed = time.time() - start_time.timestamp()
            progress = int((elapsed / (duration_minutes * 60)) * 100)
            self.set_progress(min(progress, 99))
            self.set_description(f"Logging messages... {message_count} messages sent")

            # Wait for next interval
            time.sleep(interval_seconds)

        # Write final message
        end_time_obj = datetime.now()
        print(f"[{end_time_obj.strftime('%Y-%m-%d %H:%M:%S')}] {message_prefix} - Job completed", file=sys.stdout, flush=True)
        print(f"[{end_time_obj.strftime('%Y-%m-%d %H:%M:%S')}] Total messages: {message_count}", file=sys.stdout, flush=True)

        result = {
            "job_id": self.job_id,
            "started_at": start_time.isoformat(),
            "completed_at": end_time_obj.isoformat(),
            "duration_minutes": duration_minutes,
            "interval_seconds": interval_seconds,
            "total_messages": message_count,
            "status": "completed",
        }

        self.set_progress(100)
        self.set_description(f"Completed: {message_count} messages logged")
        self.set_mcp_result(result)


class CommandExecutionJob(QueueJobBase):
    """Job that executes any MCP command in the queue.
    
    This job is designed to work with multiprocessing spawn mode (required for CUDA).
    It only stores command name and parameters (not command class/instance) to avoid
    pickle errors with module references. Command is looked up by name in child process.
    """

    def run(self) -> None:
        """Execute command in queue with progress tracking.
        
        Command is looked up by name from registry in child process to avoid
        pickle errors with module references (e.g., CUDA modules).
        """
        import asyncio
        
        self.logger.info(
            f"CommandExecutionJob {self.job_id}: Starting command execution"
        )
        
        command_name = self.mcp_params.get("command")
        command_params = self.mcp_params.get("params", {}) or {}
        context = self.mcp_params.get("context", {})
        
        if not command_name:
            self.set_mcp_error("Command name is required")
            return
        
        try:
            # Import registry in child process to avoid pickle issues
            # This allows CUDA and other modules to be initialized fresh in child process
            from mcp_proxy_adapter.commands.command_registry import registry
            
            # Get command class by name (not passed via pickle)
            command_class = registry.get_command(command_name)
            
            if command_class is None:
                self.set_mcp_error(f"Command '{command_name}' not found in registry")
                return
            
            # Set initial status
            self.set_status("running")
            self.set_description(f"Executing command: {command_name}")
            self.set_progress(0)
            
            # Execute command asynchronously with context and progress tracking
            async def _execute_with_progress():
                # Check if command has duration parameter for progress tracking
                duration = command_params.get("duration", 0)
                steps = command_params.get("steps", 10)
                
                if duration > 0 and steps > 0:
                    # Long-running command with progress updates
                    # Execute command in background and update progress in parallel
                    self.set_progress(5)
                    self.set_description(f"Starting {command_name}...")
                    
                    # Create task for command execution
                    command_task = asyncio.create_task(
                        command_class.run(**command_params, context=context)
                    )
                    
                    # Update progress while command is running
                    step_duration = max(0.1, duration / steps)
                    start_time = asyncio.get_event_loop().time()
                    
                    for i in range(steps):
                        # Wait a bit
                        await asyncio.sleep(step_duration)
                        
                        # Calculate progress based on time elapsed
                        elapsed = asyncio.get_event_loop().time() - start_time
                        progress = min(90, int((elapsed / duration) * 90))  # Leave 10% for completion
                        
                        self.set_progress(progress)
                        self.set_description(
                            f"Executing {command_name}: step {i+1}/{steps} ({progress}%)"
                        )
                        
                        # Check if command is done
                        if command_task.done():
                            break
                    
                    # Wait for command to complete
                    self.set_progress(95)
                    self.set_description(f"Finalizing {command_name}...")
                    result = await command_task
                    self.set_progress(100)
                    self.set_description(f"Command {command_name} completed")
                    return result
                else:
                    # Regular command execution
                    self.set_progress(50)
                    self.set_description(f"Executing command: {command_name}")
                    result = await command_class.run(**command_params, context=context)
                    self.set_progress(100)
                    self.set_description(f"Command {command_name} completed")
                    return result
            
            # Run in event loop - handle both cases
            try:
                # Try to get existing event loop
                loop = asyncio.get_running_loop()
                # If we're in an async context, we need to use a different approach
                # Create a new task in the existing loop
                import concurrent.futures
                with concurrent.futures.ThreadPoolExecutor() as executor:
                    future = executor.submit(
                        lambda: asyncio.run(_execute_with_progress())
                    )
                    result_obj = future.result(timeout=300)  # 5 minute timeout
            except RuntimeError:
                # No event loop running, create new one
                result_obj = asyncio.run(_execute_with_progress())
            
            result_dict = result_obj.to_dict() if hasattr(result_obj, "to_dict") else {"result": str(result_obj)}
            
            self.set_mcp_result({
                "job_id": self.job_id,
                "command": command_name,
                "result": result_dict,
                "status": "completed",
            })
        except Exception as e:
            self.logger.exception(f"Command execution failed: {e}")
            self.set_mcp_error(f"Command execution failed: {str(e)}")


class FileDownloadJob(QueueJobBase):
    """Example file download job with progress tracking."""

    def run(self) -> None:
        """Execute file download job."""
        self.logger.info(
            f"FileDownloadJob {self.job_id}: Starting file download"
        )

        url = self.mcp_params.get("url", "https://example.com/file.zip")
        file_size = self.mcp_params.get(
            "file_size", 1024 * 1024
        )  # Default 1MB

        self.set_status("running")
        self.set_description(f"Downloading {url}...")

        # Simulate download with progress updates
        downloaded = 0
        chunk_size = 64 * 1024  # 64KB chunks

        while downloaded < file_size:
            # Simulate download chunk
            chunk = min(chunk_size, file_size - downloaded)
            time.sleep(0.1)  # Simulate network delay

            downloaded += chunk
            progress = int(downloaded / file_size * 100)

            self.set_progress(progress)
            self.set_description(
                f"Downloading {url}... {progress}% complete "
                f"({downloaded}/{file_size} bytes)"
            )

            # Simulate occasional network errors (3% chance)
            if random.random() < 0.03:
                self.set_mcp_error(
                    f"Network error during download at {progress}%", "failed"
                )
                return

        # Complete successfully
        result = {
            "job_id": self.job_id,
            "url": url,
            "file_size": file_size,
            "downloaded_bytes": downloaded,
            "completed_at": time.time(),
            "status": "completed",
        }

        self.set_mcp_result(result)

