"""
Test Execution API Routes
"""

import json
import uuid
from datetime import datetime
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, HTTPException
from pydantic import BaseModel
from typing import Optional, List, Dict, Any

from opentester.core.execution_engine import execution_manager, ExecutionStatus
from opentester.core.storage import get_project_storage, get_storage
from opentester.models.project import TargetType

router = APIRouter()
storage = get_storage()


class ExecutionRequest(BaseModel):
    """Request model for starting execution."""
    project_id: str
    case_id: str


class StepUpdate(BaseModel):
    """Model for step update from executor."""
    step_index: int
    action: str
    status: str
    message: str = ""
    duration_ms: int = 0


class ExecutionResponse(BaseModel):
    """Response model for execution status."""
    execution_id: str
    project_id: str = ""
    case_id: str = ""
    status: str
    message: str
    current_step: int = 0
    total_steps: int = 0
    progress_percentage: int = 0


@router.post("/run", response_model=ExecutionResponse)
async def run_execution(data: ExecutionRequest):
    """Start test execution."""
    from opentester.models.project import TargetType

    # Load project to get target type
    project = storage.get_project(data.project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    # Load test case
    case = storage.get_case(data.case_id)
    if not case:
        raise HTTPException(status_code=404, detail="Test case not found")

    # Parse DSL to get steps and variables
    dsl_content = case.get("dsl_content", "")
    variables = {}
    steps = []

    if not dsl_content:
        raise HTTPException(status_code=400, detail="Test case has no DSL content. Please edit the case and add DSL steps.")

    try:
        import yaml
        dsl_data = yaml.safe_load(dsl_content)
        if dsl_data is None:
            raise HTTPException(status_code=400, detail="DSL content is empty or invalid YAML")
        steps = dsl_data.get("steps", [])
        # Extract variables from DSL if defined
        dsl_vars = dsl_data.get("vars", {})
        if dsl_vars:
            variables = dsl_vars
    except yaml.YAMLError as e:
        raise HTTPException(status_code=400, detail=f"DSL YAML parse error: {str(e)}")
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Failed to parse DSL: {str(e)}")

    if not steps:
        raise HTTPException(status_code=400, detail="No steps found in test case DSL. Ensure your YAML has a 'steps:' section.")

    # Determine target type from project (TestProject object)
    if hasattr(project, 'target') and hasattr(project.target, 'type'):
        target_type_str = project.target.type.value
    else:
        target_type_str = "cli"
    try:
        target_type = TargetType(target_type_str)
    except ValueError:
        target_type = TargetType.CLI

    # Create execution
    execution_id = execution_manager.create_execution(
        project_id=data.project_id,
        case_id=data.case_id,
        total_steps=len(steps),
    )

    # Start execution in background
    import asyncio
    asyncio.create_task(execution_manager.start_execution(
        execution_id, steps, target_type, variables
    ))

    return ExecutionResponse(
        execution_id=execution_id,
        project_id=data.project_id,
        case_id=data.case_id,
        status="pending",
        message="Execution started",
        total_steps=len(steps),
    )


@router.post("/stop/{execution_id}")
async def stop_execution(execution_id: str):
    """Stop a running execution."""
    success = execution_manager.stop_execution(execution_id)
    if not success:
        raise HTTPException(status_code=400, detail="Execution not found or already completed")
    return {"message": "Execution stopped", "id": execution_id}


@router.get("/status/{execution_id}", response_model=ExecutionResponse)
async def get_execution_status(execution_id: str):
    """Get execution status."""
    # Try memory first (for active executions)
    execution = execution_manager.get_execution(execution_id)
    if execution:
        return ExecutionResponse(
            execution_id=execution.execution_id,
            project_id=execution.project_id,
            case_id=execution.case_id,
            status=execution.status.value,
            message="Execution status retrieved",
            current_step=execution.current_step,
            total_steps=execution.total_steps,
            progress_percentage=int((execution.current_step / execution.total_steps) * 100) if execution.total_steps > 0 else 0,
        )

    # Fall back to persistent storage
    from opentester.core.storage import get_execution_storage
    exec_storage = get_execution_storage()
    data = exec_storage.get_execution(execution_id)
    if not data:
        raise HTTPException(status_code=404, detail="Execution not found")

    return ExecutionResponse(
        execution_id=data["execution_id"],
        project_id=data["project_id"],
        case_id=data["case_id"],
        status=data["status"],
        message="Execution status retrieved (from history)",
        current_step=data.get("current_step", 0),
        total_steps=data.get("total_steps", 0),
        progress_percentage=int((data.get("current_step", 0) / data.get("total_steps", 1)) * 100) if data.get("total_steps", 0) > 0 else 0,
    )


@router.get("/{execution_id}/results")
async def get_execution_results(execution_id: str):
    """Get detailed execution results including all steps."""
    # Try memory first (for active executions)
    execution = execution_manager.get_execution(execution_id)
    if execution:
        return {
            "execution_id": execution.execution_id,
            "project_id": execution.project_id,
            "case_id": execution.case_id,
            "status": execution.status.value,
            "current_step": execution.current_step,
            "total_steps": execution.total_steps,
            "start_time": execution.start_time,
            "end_time": execution.end_time,
            "error_message": execution.error_message,
            "results": [
                {
                    "step_index": r.step_index,
                    "action": r.action,
                    "status": r.status.value,
                    "message": r.message,
                    "duration_ms": r.duration_ms,
                    "timestamp": r.timestamp,
                }
                for r in execution.results
            ],
        }

    # Fall back to persistent storage
    from opentester.core.storage import get_execution_storage
    exec_storage = get_execution_storage()
    data = exec_storage.get_execution(execution_id)
    if not data:
        raise HTTPException(status_code=404, detail="Execution not found")

    return {
        "execution_id": data["execution_id"],
        "project_id": data["project_id"],
        "case_id": data["case_id"],
        "status": data["status"],
        "current_step": data.get("current_step", 0),
        "total_steps": data.get("total_steps", 0),
        "start_time": data.get("start_time"),
        "end_time": data.get("end_time"),
        "error_message": data.get("error_message"),
        "results": data.get("results", []),
    }


@router.websocket("/ws/{execution_id}")
async def execution_websocket(websocket: WebSocket, execution_id: str):
    """WebSocket for real-time execution updates."""
    await websocket.accept()

    # Check if execution exists
    execution = execution_manager.get_execution(execution_id)
    if not execution:
        await websocket.send_json({
            "type": "error",
            "message": "Execution not found",
        })
        await websocket.close()
        return

    # Subscribe to updates
    async def send_update(data: Dict[str, Any]):
        await websocket.send_json(data)

    execution_manager.subscribe(execution_id, send_update)

    try:
        # Send initial state with historical results if execution is already completed
        initial_data = {
            "type": "initial",
            "execution_id": execution_id,
            "status": execution.status.value,
            "current_step": execution.current_step,
            "total_steps": execution.total_steps,
            "progress_percentage": int((execution.current_step / execution.total_steps) * 100) if execution.total_steps > 0 else 0,
            "results": [
                {
                    "index": r.step_index,
                    "action": r.action,
                    "status": r.status.value,
                    "message": r.message,
                    "duration_ms": r.duration_ms,
                    "timestamp": r.timestamp,
                    "data": r.data if hasattr(r, 'data') else {},
                }
                for r in execution.results
            ],
        }
        await websocket.send_json(initial_data)

        # Keep connection alive and listen for client commands
        while True:
            try:
                message = await websocket.receive_text()
                data = json.loads(message)

                command = data.get("command")
                if command == "stop":
                    execution_manager.stop_execution(execution_id)
                    await websocket.send_json({
                        "type": "command_result",
                        "command": "stop",
                        "success": True,
                    })
                elif command == "ping":
                    await websocket.send_json({"type": "pong"})

            except json.JSONDecodeError:
                await websocket.send_json({
                    "type": "error",
                    "message": "Invalid JSON",
                })

    except WebSocketDisconnect:
        pass
    finally:
        execution_manager.unsubscribe(execution_id, send_update)


@router.get("/project/{project_id}/history")
async def get_project_execution_history(project_id: str, limit: int = 10):
    """Get execution history for a project from persistent storage."""
    from opentester.core.storage import get_execution_storage

    exec_storage = get_execution_storage()
    executions = exec_storage.get_project_history(project_id, limit=limit)

    return {
        "executions": [
            {
                "execution_id": e["execution_id"],
                "case_id": e["case_id"],
                "status": e["status"],
                "current_step": e.get("current_step", 0),
                "total_steps": e.get("total_steps", 0),
                "start_time": e.get("start_time"),
                "end_time": e.get("end_time"),
            }
            for e in executions
        ]
    }


class BatchExecutionRequest(BaseModel):
    """Request model for batch execution."""
    project_id: str
    case_ids: List[str]


class BatchExecutionResponse(BaseModel):
    """Response model for batch execution."""
    batch_id: str
    project_id: str
    case_count: int
    status: str
    message: str


@router.post("/batch/run", response_model=BatchExecutionResponse)
async def run_batch_execution(data: BatchExecutionRequest):
    """Start batch execution of multiple test cases."""
    if not data.case_ids:
        raise HTTPException(status_code=400, detail="No test cases specified")

    # Load project
    project = storage.get_project(data.project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    # Create batch execution record
    batch_id = str(uuid.uuid4())

    # Determine target type
    if hasattr(project, 'target') and hasattr(project.target, 'type'):
        target_type_str = project.target.type.value
    else:
        target_type_str = "cli"
    try:
        target_type = TargetType(target_type_str)
    except ValueError:
        target_type = TargetType.CLI

    # Start batch execution in background
    import asyncio
    asyncio.create_task(_execute_batch(batch_id, data.project_id, data.case_ids, target_type))

    return BatchExecutionResponse(
        batch_id=batch_id,
        project_id=data.project_id,
        case_count=len(data.case_ids),
        status="running",
        message=f"Batch execution started for {len(data.case_ids)} test cases",
    )


async def _execute_batch(batch_id: str, project_id: str, case_ids: List[str], target_type: TargetType):
    """Execute multiple test cases sequentially."""
    batch_executions[batch_id] = {
        "batch_id": batch_id,
        "project_id": project_id,
        "case_ids": case_ids,
        "executions": [],
        "current_index": 0,
        "status": "running",
        "start_time": datetime.now().isoformat(),
        "end_time": None,
    }

    for i, case_id in enumerate(case_ids):
        # Update current index
        batch_executions[batch_id]["current_index"] = i

        # Load test case
        case = storage.get_case(case_id)
        if not case:
            batch_executions[batch_id]["executions"].append({
                "case_id": case_id,
                "execution_id": None,
                "status": "failed",
                "error": "Test case not found",
            })
            continue

        # Parse DSL
        dsl_content = case.get("dsl_content", "")
        variables = {}
        try:
            import yaml
            dsl_data = yaml.safe_load(dsl_content) or {}
            steps = dsl_data.get("steps", [])
            dsl_vars = dsl_data.get("vars", {})
            if dsl_vars:
                variables = dsl_vars
        except Exception:
            steps = []

        if not steps:
            batch_executions[batch_id]["executions"].append({
                "case_id": case_id,
                "execution_id": None,
                "status": "failed",
                "error": "No steps found in test case",
            })
            continue

        # Create and start execution
        execution_id = execution_manager.create_execution(
            project_id=project_id,
            case_id=case_id,
            total_steps=len(steps),
        )

        batch_executions[batch_id]["executions"].append({
            "case_id": case_id,
            "execution_id": execution_id,
            "status": "running",
        })

        # Start execution and wait for completion
        try:
            await execution_manager.start_execution(execution_id, steps, target_type, variables)

            # Get final status
            execution = execution_manager.get_execution(execution_id)
            if execution:
                batch_executions[batch_id]["executions"][-1]["status"] = execution.status.value
        except Exception as e:
            batch_executions[batch_id]["executions"][-1]["status"] = "failed"
            batch_executions[batch_id]["executions"][-1]["error"] = str(e)

    # Mark batch as completed
    batch_executions[batch_id]["status"] = "completed"
    batch_executions[batch_id]["end_time"] = datetime.now().isoformat()


# In-memory storage for batch executions
batch_executions: Dict[str, Dict] = {}


@router.get("/batch/{batch_id}")
async def get_batch_status(batch_id: str):
    """Get batch execution status."""
    if batch_id not in batch_executions:
        raise HTTPException(status_code=404, detail="Batch execution not found")

    batch = batch_executions[batch_id]
    return {
        "batch_id": batch["batch_id"],
        "project_id": batch["project_id"],
        "status": batch["status"],
        "current_index": batch["current_index"],
        "total_cases": len(batch["case_ids"]),
        "start_time": batch["start_time"],
        "end_time": batch["end_time"],
        "executions": [
            {
                "case_id": e["case_id"],
                "execution_id": e["execution_id"],
                "status": e["status"],
            }
            for e in batch["executions"]
        ],
    }
