"""
FastAPI Server for receiving execution requests from platform.
"""
import asyncio
import logging
import threading
from typing import Callable, Dict, Optional, Any
from datetime import datetime

try:
    from fastapi import FastAPI, HTTPException
    from fastapi.middleware.cors import CORSMiddleware
    from pydantic import BaseModel
    import uvicorn
except ImportError:
    raise ImportError(
        "FastAPI and uvicorn are required for server functionality. "
        "Install with: pip install 'zsynctech-studio-sdk[server]' or pip install fastapi uvicorn"
    )

# Configure logger
logger = logging.getLogger('zsynctech_studio_sdk.server')


class ExecutionRegistry:
    """Registry of available executions that can be triggered remotely"""

    def __init__(self):
        self._executions: Dict[str, Callable] = {}  # name -> function (legacy)
        self._instance_to_execution: Dict[str, Callable] = {}  # instance_id -> function
        self._instance_id: Optional[str] = None
        self._encryption_key: Optional[str] = None

    def set_instance_id(self, instance_id: str) -> None:
        """Set the instance ID for this deployment"""
        self._instance_id = instance_id

    def get_instance_id(self) -> Optional[str]:
        """Get the registered instance ID"""
        return self._instance_id

    def set_encryption_key(self, key: str) -> None:
        """Set the encryption key for this deployment"""
        self._encryption_key = key

    def get_encryption_key(self) -> Optional[str]:
        """Get the encryption key"""
        return self._encryption_key

    def register(self, name: str, func: Callable) -> None:
        """Register an execution function by name (legacy)"""
        self._executions[name] = func

    def register_by_instance(self, instance_id: str, func: Callable) -> None:
        """Register an execution function by instance_id"""
        self._instance_to_execution[instance_id] = func

    def get(self, name: str) -> Optional[Callable]:
        """Get an execution function by name"""
        return self._executions.get(name)

    def get_by_instance(self, instance_id: str) -> Optional[Callable]:
        """Get an execution function by instance_id"""
        return self._instance_to_execution.get(instance_id)

    def list_executions(self) -> list[str]:
        """List all registered executions"""
        return list(self._executions.keys())

    def list_instance_ids(self) -> list[str]:
        """List all registered instance IDs"""
        return list(self._instance_to_execution.keys())


# Global registry
_registry = ExecutionRegistry()


def register_execution(name: str):
    """
    Decorator to register an execution for remote triggering

    Usage:
        @execution
        @register_execution("my_automation")
        def my_automation():
            # your code
            pass
    """
    def decorator(func: Callable) -> Callable:
        _registry.register(name, func)
        return func
    return decorator


# Pydantic models for request/response
from enum import StrEnum


class InputOutputTypes(StrEnum):
    FTP = 'FTP'
    API = 'API'
    FILA = 'FILA'
    QUEUE = 'QUEUE'


class Credential(BaseModel):
    key: str
    value: str
    encrypted: bool


class Config(BaseModel):
    """Environment configuration received from execution requests."""
    instanceId: str
    executionId: str
    automationName: Optional[str] = "System"
    clientId: Optional[str] = None
    userId: Optional[str] = "System"
    outputPath: Optional[str] = None
    inputPath: Optional[str] = None
    inputMetaData: Optional[Dict[str, Any]] = None
    inputType: Optional[InputOutputTypes] = InputOutputTypes.FTP
    outputType: Optional[InputOutputTypes] = InputOutputTypes.FTP
    outputMetaData: Optional[Dict[str, Any]] = None
    keepAlive: Optional[bool] = False
    keepAliveInterval: Optional[int] = 30
    credentials: Optional[list[Credential]] = None


class ExecuteResponse(BaseModel):
    """Response model for execution request"""
    status: str
    execution_name: str
    execution_id: Optional[str]
    message: str
    timestamp: str


class HealthResponse(BaseModel):
    """Response model for health check"""
    status: str
    timestamp: str


# Create FastAPI app
app = FastAPI(
    title="ZSyncTech Studio Execution Server",
    description="Server for receiving execution requests from platform",
    version="0.2.0",
    docs_url=None
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/{instance_id}/health", response_model=HealthResponse)
async def health_check_with_instance(instance_id: str):
    """
    Health check endpoint for a specific instance

    Validates that the instance_id is registered.
    Example: GET /0199a0e5-0381-7e70-b464-54525bcff142/health
    """
    # Check if instance is registered
    if not _registry.get_by_instance(instance_id):
        raise HTTPException(
            status_code=404,
            detail={
                "error": f"No automation deployed for instance ID '{instance_id}'",
                "registered_instance_ids": _registry.list_instance_ids()
            }
        )

    return HealthResponse(
        status="healthy",
        timestamp=datetime.now().isoformat()
    )


def _execute_automation(config: Config, validate_instance_id: Optional[str] = None):
    """
    Internal function to execute automation

    Args:
        config: Config object from platform
        validate_instance_id: If provided, validates that config.instanceId matches this value
    """
    # Validate instance_id if required
    if validate_instance_id:
        if config.instanceId != validate_instance_id:
            raise HTTPException(
                status_code=403,
                detail={
                    "error": f"Instance ID mismatch",
                    "expected": validate_instance_id,
                    "received": config.instanceId
                }
            )

    execution_id = config.executionId
    instance_id = config.instanceId

    # Get execution function BY INSTANCE ID (not by automationName!)
    execution_func = _registry.get_by_instance(instance_id)

    if not execution_func:
        raise HTTPException(
            status_code=404,
            detail={
                "error": f"No automation deployed for instance ID '{instance_id}'",
                "registered_instance_ids": _registry.list_instance_ids()
            }
        )

    # Get automation name for logging
    execution_name = config.automationName or "unknown"

    # Execute in background thread
    def run_execution():
        try:
            # Convert Pydantic credentials to config.Credential
            from .config import (
                set_deployment_config,
                EnvironmentConfig,
                Credential as ConfigCredential,
                InputOutputTypes as ConfigInputOutputTypes
            )

            credentials_list = []
            if config.credentials:
                for cred in config.credentials:
                    credentials_list.append(ConfigCredential(
                        key=cred.key,
                        value=cred.value,
                        encrypted=cred.encrypted
                    ))

            # Create EnvironmentConfig object
            env_config = EnvironmentConfig(
                instanceId=config.instanceId,
                executionId=config.executionId,
                automationName=config.automationName or "System",
                clientId=config.clientId or "",
                userId=config.userId or "System",
                outputPath=config.outputPath or "",
                inputPath=config.inputPath or "",
                inputMetaData=config.inputMetaData,
                inputType=ConfigInputOutputTypes(config.inputType.value) if config.inputType else ConfigInputOutputTypes.FTP,
                outputType=ConfigInputOutputTypes(config.outputType.value) if config.outputType else ConfigInputOutputTypes.FTP,
                outputMetaData=config.outputMetaData,
                keepAlive=config.keepAlive or False,
                keepAliveInterval=config.keepAliveInterval,
                credentials=credentials_list
            )

            # Get encryption key from registry (set during deploy)
            encryption_key = _registry.get_encryption_key()
            if encryption_key:
                env_config.set_encryption_key(encryption_key)

            # Store config globally for access anywhere in the code
            set_deployment_config(
                instance_id=config.instanceId,
                execution_id=config.executionId,
                encryption_key=encryption_key,
                _env_config=env_config
            )

            result = execution_func(execution_id=execution_id)

        except Exception as e:
            logger.error(f"Execution '{execution_name}' - Failed with error: {str(e)}")
            import traceback
            traceback.print_exc()

    thread = threading.Thread(target=run_execution, daemon=True)
    thread.start()

    return ExecuteResponse(
        status="accepted",
        execution_name=execution_name,
        execution_id=execution_id,
        message=f"Execution '{execution_name}' started",
        timestamp=datetime.now().isoformat()
    )


@app.post("/{instance_id}/start", response_model=ExecuteResponse)
async def start_execution_with_instance(instance_id: str, config: Config):
    """
    Trigger an execution for a specific instance

    The instance_id in the URL must match the instanceId in the config body.

    Example: POST /0199a0e5-0381-7e70-b464-54525bcff142/start

    Request body (Config object sent directly):
    {
        "instanceId": "0199a0e5-0381-7e70-b464-54525bcff142",
        "executionId": "exec-456",
        "automationName": "Robô Santander Pré Aprovados",
        "clientId": "client-789",
        "userId": "user-001",
        "inputPath": "/path/to/input",
        "outputPath": "/path/to/output",
        "inputType": "FTP",
        "outputType": "QUEUE",
        "credentials": [
            {"key": "api_key", "value": "secret", "encrypted": true}
        ]
    }
    """
    return _execute_automation(config, validate_instance_id=instance_id)


class ExecutionServer:
    """FastAPI server for receiving execution requests"""

    def __init__(self, host: str = '0.0.0.0', port: int = 8080):
        self.host = host
        self.port = port
        self.server = None
        self.thread = None

    def start(self, blocking: bool = True):
        """
        Start the execution server

        Args:
            blocking: If True, blocks the current thread. If False, runs in background.
        """
        instance_id = _registry.get_instance_id()

        logger.info(f"Starting execution server on http://{self.host}:{self.port}")
        if instance_id:
            logger.info(f"Server ready - Instance ID: {instance_id[:13]}...")

        config = uvicorn.Config(
            app,
            host=self.host,
            port=self.port,
            log_level="info"
        )

        if blocking:
            server = uvicorn.Server(config)
            server.run()
        else:
            self.thread = threading.Thread(
                target=self._run_in_thread,
                args=(config,),
                daemon=True
            )
            self.thread.start()

    def _run_in_thread(self, config):
        """Run uvicorn server in thread"""
        server = uvicorn.Server(config)
        asyncio.run(server.serve())

    def stop(self):
        """Stop the server"""
        logger.info("Execution server shutting down")


def serve(host: str = '0.0.0.0', port: int = 8080, blocking: bool = True) -> ExecutionServer:
    """
    Start an execution server

    Args:
        host: Host to bind to (default: 0.0.0.0)
        port: Port to listen on (default: 8080)
        blocking: If True, blocks until stopped. If False, runs in background.

    Returns:
        ExecutionServer instance

    Example:
        from zsynctech_studio_sdk import execution, register_execution, serve

        @execution
        @register_execution("my_automation")
        def my_automation():
            # your code
            pass

        # Start server (blocks)
        serve(port=8080)
    """
    server = ExecutionServer(host, port)
    server.start(blocking=blocking)
    return server
