"""FastAPI application for HEDit annotation service.

This module provides REST API endpoints for HED annotation generation
and validation using the multi-agent workflow.
"""

import asyncio
import hashlib
import json
import logging
import os
import time
from contextlib import asynccontextmanager
from pathlib import Path

from dotenv import load_dotenv
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from langchain_community.chat_models import ChatOllama

from src import __version__
from src.agents.vision_agent import VisionAgent
from src.agents.workflow import HedAnnotationWorkflow
from src.api.models import (
    AnnotationRequest,
    AnnotationResponse,
    FeedbackRequest,
    FeedbackResponse,
    HealthResponse,
    ImageAnnotationRequest,
    ImageAnnotationResponse,
    ValidationRequest,
    ValidationResponse,
)
from src.api.security import api_key_auth, audit_logger
from src.telemetry import LocalFileStorage, TelemetryCollector, TelemetryEvent
from src.utils.openrouter_llm import create_openrouter_llm, get_model_name
from src.utils.schema_loader import HedSchemaLoader
from src.validation.hed_validator import HedPythonValidator

# Load environment variables from .env file
load_dotenv()

# Global workflow and vision agent instances
workflow: HedAnnotationWorkflow | None = None
vision_agent: VisionAgent | None = None
schema_loader: HedSchemaLoader | None = None

# Telemetry collector (initialized in lifespan)
telemetry_collector: TelemetryCollector | None = None

# Cache for BYOK configuration
_byok_config: dict = {}


def _derive_user_id(token: str) -> str:
    """Derive a stable user ID from API token for cache optimization.

    Uses PBKDF2 to create a stable, anonymous identifier from the token.
    Each unique token gets its own cache lane in OpenRouter.

    Note: While PBKDF2 is designed for password hashing, we use it here
    to satisfy CodeQL requirements. The token is already high-entropy,
    so the computational cost is primarily for compliance.

    Args:
        token: OpenRouter API token (already a secret, not user password)

    Returns:
        16-character hexadecimal cache ID
    """
    # PBKDF2 is a computationally expensive KDF that satisfies CodeQL
    # Using minimal iterations (1000) since input is already high-entropy
    salt = b"hedit-cache-id-v1"
    derived = hashlib.pbkdf2_hmac("sha256", token.encode(), salt, iterations=1000, dklen=8)
    return derived.hex()


def create_openrouter_workflow(
    api_key: str,
    annotation_model: str | None = None,
    annotation_provider: str | None = None,
    eval_model: str | None = None,
    eval_provider: str | None = None,
    temperature: float | None = None,
    user_id: str | None = None,
    schema_dir: str | Path | None = None,
    validator_path: str | Path | None = None,
    use_js_validator: bool = True,
) -> HedAnnotationWorkflow:
    """Create a workflow with OpenRouter LLMs.

    Unified function for both BYOK and server modes. Applies defaults from
    environment variables, then overrides with provided parameters.

    Args:
        api_key: OpenRouter API key
        annotation_model: Model for annotation (default: ANNOTATION_MODEL env or Claude Haiku 4.5)
        annotation_provider: Provider for annotation model (default: ANNOTATION_PROVIDER env or "anthropic")
        eval_model: Model for eval/assessment/feedback (default: EVALUATION_MODEL env or Qwen3-235B)
        eval_provider: Provider for eval models (default: EVALUATION_PROVIDER env or auto-routed)
        temperature: LLM temperature (default: 0.1)
        user_id: User ID for cache optimization (derived from API key if not provided)
        schema_dir: Path to HED schemas (None = fetch from GitHub)
        validator_path: Path to hed-javascript (None = use auto fallback chain)
        use_js_validator: Whether to use JavaScript validator

    Returns:
        Configured HedAnnotationWorkflow
    """
    # Apply defaults from environment
    default_annotation_model = os.getenv("ANNOTATION_MODEL", "anthropic/claude-haiku-4.5")
    default_annotation_provider = os.getenv("ANNOTATION_PROVIDER", "anthropic")
    default_eval_model = os.getenv("EVALUATION_MODEL", "qwen/qwen3-235b-a22b-2507")
    default_eval_provider = os.getenv("EVALUATION_PROVIDER", "")

    # Resolve final values: parameter > env var > default
    actual_annotation_model = get_model_name(annotation_model or default_annotation_model)
    actual_eval_model = get_model_name(eval_model or default_eval_model)
    actual_temperature = temperature if temperature is not None else 0.1
    actual_user_id = user_id or _derive_user_id(api_key)

    # Provider logic: if model specified without provider, clear provider
    # (to avoid using wrong provider for custom models)
    if annotation_provider is not None:
        actual_annotation_provider = annotation_provider if annotation_provider else None
    elif annotation_model is not None:
        actual_annotation_provider = None  # Custom model, no default provider
    else:
        actual_annotation_provider = default_annotation_provider

    actual_eval_provider = eval_provider or default_eval_provider or None

    # Create LLMs
    annotation_llm = create_openrouter_llm(
        model=actual_annotation_model,
        api_key=api_key,
        temperature=actual_temperature,
        provider=actual_annotation_provider,
        user_id=actual_user_id,
    )
    evaluation_llm = create_openrouter_llm(
        model=actual_eval_model,
        api_key=api_key,
        temperature=actual_temperature,
        provider=actual_eval_provider,
        user_id=actual_user_id,
    )
    assessment_llm = create_openrouter_llm(
        model=actual_eval_model,
        api_key=api_key,
        temperature=actual_temperature,
        provider=actual_eval_provider,
        user_id=actual_user_id,
    )
    feedback_llm = create_openrouter_llm(
        model=actual_eval_model,
        api_key=api_key,
        temperature=actual_temperature,
        provider=actual_eval_provider,
        user_id=actual_user_id,
    )

    # Create and return workflow
    # Only use JS validator if validator_path is available
    # Note: Semantic search now uses hed-lsp CLI instead of keyword extraction LLM
    actual_use_js = use_js_validator and validator_path is not None
    return HedAnnotationWorkflow(
        llm=annotation_llm,
        evaluation_llm=evaluation_llm,
        assessment_llm=assessment_llm,
        feedback_llm=feedback_llm,
        schema_dir=Path(schema_dir) if schema_dir else None,
        validator_path=Path(validator_path) if validator_path else None,
        use_js_validator=actual_use_js,
    )


def create_byok_workflow(
    openrouter_key: str,
    model: str | None = None,
    provider: str | None = None,
    eval_model: str | None = None,
    eval_provider: str | None = None,
    temperature: float | None = None,
    user_id_override: str | None = None,
) -> HedAnnotationWorkflow:
    """Create a workflow for BYOK mode using the user's OpenRouter key.

    Thin wrapper around create_openrouter_workflow that uses cached server config
    for schema/validator paths.

    Args:
        openrouter_key: User's OpenRouter API key
        model: Override annotation model
        provider: Override annotation provider
        eval_model: Override evaluation model (for all eval/assessment/feedback)
        eval_provider: Override evaluation provider
        temperature: Override LLM temperature
        user_id_override: Custom user ID for cache optimization

    Returns:
        Configured HedAnnotationWorkflow using the user's key
    """
    global _byok_config

    return create_openrouter_workflow(
        api_key=openrouter_key,
        annotation_model=model,
        annotation_provider=provider,
        eval_model=eval_model,
        eval_provider=eval_provider,
        temperature=temperature if temperature is not None else _byok_config.get("temperature"),
        user_id=user_id_override,
        schema_dir=_byok_config.get("schema_dir"),
        validator_path=_byok_config.get("validator_path"),
        use_js_validator=_byok_config.get("use_js_validator", True),
    )


def create_byok_vision_agent(
    openrouter_key: str,
    vision_model: str | None = None,
    provider: str | None = None,
    temperature: float | None = None,
    user_id_override: str | None = None,
) -> VisionAgent:
    """Create a vision agent instance using the user's OpenRouter key (BYOK mode).

    Args:
        openrouter_key: User's OpenRouter API key
        vision_model: Override vision model (uses server default if None)
        provider: Override provider preference (uses server default if None)
        temperature: Override temperature (uses 0.3 default if None)
        user_id_override: Custom user ID for cache optimization (overrides API key derived ID)

    Returns:
        Configured VisionAgent using the user's key and model settings
    """
    # Use user-provided settings or fall back to server defaults
    default_vision_model = os.getenv("VISION_MODEL", "qwen/qwen3-vl-30b-a3b-instruct")
    default_vision_provider = os.getenv("VISION_PROVIDER", "deepinfra/fp8")

    actual_model = vision_model if vision_model else default_vision_model
    actual_temperature = temperature if temperature is not None else 0.3

    # Provider logic:
    # - If user specifies a custom vision model, clear provider
    # - Unless user also explicitly specifies a provider
    if provider is not None:
        actual_provider = provider if provider else None
    elif vision_model is not None:
        # Custom vision model → clear provider
        actual_provider = None
    else:
        actual_provider = default_vision_provider

    # Use custom user_id if provided, otherwise derive from API key
    user_id = user_id_override or _derive_user_id(openrouter_key)

    vision_llm = create_openrouter_llm(
        model=actual_model,
        api_key=openrouter_key,
        temperature=actual_temperature,
        provider=actual_provider,
        user_id=user_id,
    )

    return VisionAgent(llm=vision_llm)


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Manage application lifespan (startup and shutdown).

    Args:
        app: FastAPI application
    """
    global workflow, vision_agent, schema_loader

    # Startup: Initialize workflow
    print("Initializing HEDit annotation workflow...")

    # Auto-detect environment (Docker vs local)
    def get_default_path(docker_path: str, local_path: str) -> str:
        """Get default path based on environment.

        Args:
            docker_path: Path to use in Docker
            local_path: Path to use in local development

        Returns:
            Appropriate default path, or None if no paths exist
            (HED library will fetch from GitHub when None)
        """
        # Check if running in Docker (look for Docker-specific paths)
        if Path("/app").exists() and Path(docker_path).exists():
            return docker_path
        # Check if local development path exists
        elif Path(local_path).exists():
            return local_path
        # Return None to trigger HED library to fetch from GitHub
        return None

    # Get configuration from environment with smart defaults
    llm_provider = os.getenv("LLM_PROVIDER", "ollama")  # "ollama" or "openrouter"
    llm_temperature = float(os.getenv("LLM_TEMPERATURE", "0.1"))

    # Schema directory with environment detection
    schema_dir = os.getenv(
        "HED_SCHEMA_DIR",
        get_default_path(
            "/app/hed-schemas/schemas_latest_json",  # Docker
            str(Path.home() / "git/hed-schemas/schemas_latest_json"),  # Local Linux/macOS
        ),
    )

    # Validator path with environment detection
    validator_path = os.getenv(
        "HED_VALIDATOR_PATH",
        get_default_path(
            "/app/hed-javascript",  # Docker
            str(Path.home() / "git/hed-javascript"),  # Local Linux/macOS
        ),
    )

    use_js_validator = os.getenv("USE_JS_VALIDATOR", "true").lower() == "true"

    # Cache BYOK configuration for on-demand workflow creation
    global _byok_config
    _byok_config = {
        "temperature": llm_temperature,
        "provider_preference": os.getenv("LLM_PROVIDER_PREFERENCE"),
        "schema_dir": schema_dir,
        "validator_path": validator_path,
        "use_js_validator": use_js_validator,
    }

    print(f"Environment: {'Docker' if Path('/app').exists() else 'Local'}")
    print(f"Schema directory: {schema_dir or 'GitHub (dynamic fetch)'}")
    print(f"Validator path: {validator_path or 'None (using Python validator)'}")

    # Initialize workflow based on provider
    if llm_provider == "openrouter":
        # OpenRouter configuration - use unified workflow creation
        openrouter_api_key = os.getenv("OPENROUTER_API_KEY")
        if not openrouter_api_key:
            raise ValueError(
                "OPENROUTER_API_KEY environment variable is required when using OpenRouter"
            )

        # Log configuration (env vars are read by create_openrouter_workflow)
        print("Using OpenRouter with models:")
        print(f"  Annotation: {os.getenv('ANNOTATION_MODEL', 'anthropic/claude-haiku-4.5')}")
        print(f"  Evaluation: {os.getenv('EVALUATION_MODEL', 'qwen/qwen3-235b-a22b-2507')}")
        print(f"  Provider (annotation): {os.getenv('ANNOTATION_PROVIDER', 'anthropic')}")
        print(f"  Provider (eval): {os.getenv('EVALUATION_PROVIDER', '') or '(auto-routed)'}")

        workflow = create_openrouter_workflow(
            api_key=openrouter_api_key,
            temperature=llm_temperature,
            schema_dir=schema_dir,
            validator_path=validator_path if use_js_validator else None,
            use_js_validator=use_js_validator,
        )
    else:
        # Ollama configuration (default)
        llm_base_url = os.getenv("LLM_BASE_URL", "http://localhost:11435")
        llm_model = os.getenv("LLM_MODEL", "gpt-oss:20b")

        llm = ChatOllama(
            base_url=llm_base_url,
            model=llm_model,
            temperature=llm_temperature,
        )

        print(f"Using Ollama: {llm_model} at {llm_base_url}")

        # Ollama uses same LLM for all agents
        workflow = HedAnnotationWorkflow(
            llm=llm,
            schema_dir=Path(schema_dir) if schema_dir else None,
            validator_path=Path(validator_path) if use_js_validator and validator_path else None,
            use_js_validator=use_js_validator,
        )

    # Set global schema_loader from workflow
    schema_loader = workflow.schema_loader

    print("Workflow initialized successfully!")
    print(f"  LLM Provider: {llm_provider} (temperature={llm_temperature})")
    print(f"  JavaScript validator: {use_js_validator}")

    # Initialize vision agent (only for OpenRouter)
    if llm_provider == "openrouter":
        vision_model = os.getenv("VISION_MODEL", "qwen/qwen3-vl-30b-a3b-instruct")
        vision_provider = os.getenv("VISION_PROVIDER", "deepinfra/fp8")

        print(f"Initializing vision model: {vision_model} (provider: {vision_provider})")

        vision_llm = create_openrouter_llm(
            model=vision_model,
            api_key=openrouter_api_key,
            temperature=0.3,  # Slightly higher temperature for more descriptive text
            provider=vision_provider,
        )

        vision_agent = VisionAgent(llm=vision_llm)
        print("Vision agent initialized successfully!")
    else:
        print("Vision model not available (only supported with OpenRouter)")

    # Initialize telemetry collector
    global telemetry_collector
    # Use /app/telemetry in Docker, otherwise use local .hedit/telemetry
    default_telemetry_dir = "/app/telemetry" if Path("/app").exists() else ".hedit/telemetry"
    telemetry_dir = os.getenv("TELEMETRY_DIR", default_telemetry_dir)
    telemetry_storage = LocalFileStorage(storage_dir=telemetry_dir)
    telemetry_collector = TelemetryCollector(
        storage=telemetry_storage,
        enabled=True,  # Can be configured via env var if needed
    )
    print(f"Telemetry collector initialized (storage: {telemetry_dir})")

    yield

    # Shutdown
    print("Shutting down HEDit...")


# Create FastAPI app
app = FastAPI(
    title="HEDit API",
    description="Multi-agent system for HED annotation generation and validation",
    version=__version__,
    lifespan=lifespan,
)

# Configure CORS
# Production: Strict origin validation
# Development: Allow all localhost ports for easy local testing
allowed_origins = [
    "https://hedit.pages.dev",  # Production frontend
    "https://develop.hedit.pages.dev",  # Development frontend
    "https://hedit-api.shirazi-10f.workers.dev",  # Production Worker proxy
    "https://hedit-dev-api.shirazi-10f.workers.dev",  # Development Worker proxy
    "https://annotation.garden",  # Main AGI website
]

# Add common localhost ports for development
# These allow testing with any local dev server
localhost_origins = [
    "http://localhost:3000",  # React default
    "http://localhost:5173",  # Vite default
    "http://localhost:8080",  # Common dev server
    "http://localhost:8000",  # Alternative
    "http://127.0.0.1:3000",  # IPv4 localhost
    "http://127.0.0.1:5173",
    "http://127.0.0.1:8080",
    "http://127.0.0.1:8000",
]

# Add localhost origins (can be disabled via env var for strict production)
if os.getenv("ALLOW_LOCALHOST_CORS", "true").lower() == "true":
    allowed_origins.extend(localhost_origins)

# Add environment-specific origins if configured
if extra_origins := os.getenv("EXTRA_CORS_ORIGINS"):
    allowed_origins.extend(
        [origin.strip() for origin in extra_origins.split(",") if origin.strip()]
    )

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=allowed_origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=[
        "Content-Type",
        "Authorization",
        "X-Requested-With",
        "X-API-Key",
        "X-OpenRouter-Key",  # BYOK mode
        "X-OpenRouter-Model",  # BYOK model override
        "X-OpenRouter-Vision-Model",  # BYOK vision model override
        "X-OpenRouter-Provider",  # BYOK provider preference
        "X-OpenRouter-Temperature",  # BYOK temperature override
        "X-OpenRouter-Eval-Model",  # BYOK eval model override
        "X-OpenRouter-Eval-Provider",  # BYOK eval provider override
        "X-User-Id",  # Custom user ID for cache optimization
    ],
    max_age=3600,  # Cache preflight requests for 1 hour
)


# Audit logging middleware
@app.middleware("http")
async def audit_logging_middleware(request: Request, call_next):
    """Middleware to log all requests and responses for audit trail."""
    start_time = time.time()

    # Log incoming request
    api_key = request.headers.get("x-api-key")
    api_key_hash = api_key[:8] + "..." if api_key else None
    audit_logger.log_request(request, api_key_hash=api_key_hash)

    # Process request
    try:
        response = await call_next(request)
        processing_time_ms = (time.time() - start_time) * 1000

        # Log response
        audit_logger.log_response(request, response.status_code, processing_time_ms)

        # Add security headers
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"

        return response
    except Exception as e:
        # Log error
        audit_logger.log_error(request, e, api_key_hash=api_key_hash)
        raise


@app.get("/health", response_model=HealthResponse)
async def health_check() -> HealthResponse:
    """Health check endpoint.

    Returns:
        Health status and service availability
    """
    llm_available = workflow is not None
    validator_available = schema_loader is not None

    status = "healthy" if (llm_available and validator_available) else "degraded"

    return HealthResponse(
        status=status,
        version=__version__,
        llm_available=llm_available,
        validator_available=validator_available,
    )


@app.post("/annotate", response_model=AnnotationResponse)
async def annotate(
    request: AnnotationRequest,
    req: Request,
    api_key: str = Depends(api_key_auth),
) -> AnnotationResponse:
    """Generate HED annotation from natural language description.

    Supports two authentication modes:
    - X-API-Key header: Server-level authentication
    - X-OpenRouter-Key header: BYOK mode (uses your OpenRouter key for billing)

    Args:
        request: Annotation request with description and parameters
        req: FastAPI request to extract headers
        api_key: Authentication result (injected by dependency)

    Returns:
        Generated annotation with validation and assessment feedback

    Raises:
        HTTPException: If workflow fails or authentication fails
    """
    # Determine which workflow to use
    # Check for model override headers (from frontend dropdown or CLI)
    model_override = request.model or req.headers.get("x-openrouter-model")
    provider_override = request.provider or req.headers.get("x-openrouter-provider")
    eval_model_override = req.headers.get("x-openrouter-eval-model")
    eval_provider_override = req.headers.get("x-openrouter-eval-provider")
    temp_header = req.headers.get("x-openrouter-temperature")
    temperature = request.temperature
    if temperature is None and temp_header:
        try:
            temperature = float(temp_header)
        except ValueError:
            pass  # Invalid header value, use default
    user_id_override = req.headers.get("x-user-id")

    if api_key == "byok":
        # BYOK mode: Create workflow with user's key and model settings
        openrouter_key = req.headers.get("x-openrouter-key")
        if not openrouter_key:
            raise HTTPException(status_code=401, detail="Missing X-OpenRouter-Key header")

        try:
            active_workflow = create_byok_workflow(
                openrouter_key,
                model=model_override,
                provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id_override=user_id_override,
            )
        except Exception as e:
            raise HTTPException(
                status_code=500, detail=f"Failed to initialize BYOK workflow: {str(e)}"
            ) from e
    elif model_override or provider_override:
        # Server mode with model overrides: Create custom workflow with server's API key
        # This supports frontend model dropdown without requiring user's own API key
        server_api_key = os.getenv("OPENROUTER_API_KEY")
        if not server_api_key:
            raise HTTPException(
                status_code=503,
                detail="Server not configured for model overrides (missing OPENROUTER_API_KEY)",
            )

        try:
            active_workflow = create_openrouter_workflow(
                api_key=server_api_key,
                annotation_model=model_override,
                annotation_provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id=user_id_override,
                schema_dir=_byok_config.get("schema_dir"),
                validator_path=_byok_config.get("validator_path"),
                use_js_validator=_byok_config.get("use_js_validator", True),
            )
        except Exception as e:
            raise HTTPException(
                status_code=500,
                detail=f"Failed to initialize workflow with model override: {str(e)}",
            ) from e
    else:
        # Server mode: Use pre-initialized workflow
        if workflow is None:
            raise HTTPException(status_code=503, detail="Workflow not initialized")
        active_workflow = workflow

    try:
        # Run annotation workflow with increased recursion limit for long descriptions
        # LangGraph default is 25, increase to 100 for complex workflows
        config = {"recursion_limit": 100}

        start_time = time.time()
        final_state = await active_workflow.run(
            input_description=request.description,
            schema_version=request.schema_version,
            max_validation_attempts=request.max_validation_attempts,
            run_assessment=request.run_assessment,
            config=config,
        )
        latency_ms = int((time.time() - start_time) * 1000)

        # Determine overall status
        # IMPORTANT: Ensure is_valid is only True when there are NO validation errors
        # This is a safeguard to prevent inconsistencies in the workflow
        is_valid = final_state["is_valid"] and len(final_state["validation_errors"]) == 0
        status = "success" if is_valid else "failed"

        # Collect telemetry if enabled
        if request.telemetry_enabled and telemetry_collector:
            # Get model info from request body, BYOK headers, or server config
            model_name = (
                request.model
                or req.headers.get("x-openrouter-model")
                or os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
            )
            temperature = (
                request.temperature
                or float(req.headers.get("x-openrouter-temperature", 0))
                or _byok_config.get("temperature", 0.1)
            )

            event = TelemetryEvent.create(
                description=request.description,
                schema_version=request.schema_version,
                hed_string=final_state["current_annotation"],
                iterations=final_state["validation_attempts"],
                validation_errors=final_state["validation_errors"],
                model=model_name,
                provider=request.provider or req.headers.get("x-openrouter-provider"),
                temperature=temperature,
                latency_ms=latency_ms,
                source="api",
            )
            await telemetry_collector.collect(event)

        return AnnotationResponse(
            annotation=final_state["current_annotation"],
            is_valid=is_valid,
            is_faithful=final_state["is_faithful"],
            is_complete=final_state["is_complete"],
            validation_attempts=final_state["validation_attempts"],
            validation_errors=final_state["validation_errors"],
            validation_warnings=final_state["validation_warnings"],
            evaluation_feedback=final_state["evaluation_feedback"],
            assessment_feedback=final_state["assessment_feedback"],
            status=status,
        )

    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Annotation workflow failed: {str(e)}",
        ) from e


@app.post("/annotate-from-image", response_model=ImageAnnotationResponse)
async def annotate_from_image(
    request: ImageAnnotationRequest,
    req: Request,
    api_key: str = Depends(api_key_auth),
) -> ImageAnnotationResponse:
    """Generate HED annotation from an image.

    Supports two authentication modes:
    - X-API-Key header: Server-level authentication
    - X-OpenRouter-Key header: BYOK mode (uses your OpenRouter key for billing)

    This endpoint uses a vision-language model to generate a description of the image,
    then passes that description through the standard HED annotation workflow.

    Args:
        request: Image annotation request with base64 image and parameters
        req: FastAPI request to extract headers
        api_key: Authentication result (injected by dependency)

    Returns:
        Generated annotation with image description and validation feedback

    Raises:
        HTTPException: If workflow or vision agent fails or authentication fails
    """
    # Determine which workflow and vision agent to use
    # Check for model override headers (from frontend dropdown or CLI)
    model_override = request.model or req.headers.get("x-openrouter-model")
    vision_model_override = request.vision_model or req.headers.get("x-openrouter-vision-model")
    provider_override = request.provider or req.headers.get("x-openrouter-provider")
    eval_model_override = req.headers.get("x-openrouter-eval-model")
    eval_provider_override = req.headers.get("x-openrouter-eval-provider")
    temp_header = req.headers.get("x-openrouter-temperature")
    temperature = request.temperature
    if temperature is None and temp_header:
        try:
            temperature = float(temp_header)
        except ValueError:
            pass  # Invalid header value, use default
    user_id_override = req.headers.get("x-user-id")

    if api_key == "byok":
        # BYOK mode: Create workflow and vision agent with user's key and model settings
        openrouter_key = req.headers.get("x-openrouter-key")
        if not openrouter_key:
            raise HTTPException(status_code=401, detail="Missing X-OpenRouter-Key header")

        try:
            active_workflow = create_byok_workflow(
                openrouter_key,
                model=model_override,
                provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id_override=user_id_override,
            )
            active_vision_agent = create_byok_vision_agent(
                openrouter_key,
                vision_model=vision_model_override,
                provider=provider_override,
                temperature=temperature,
                user_id_override=user_id_override,
            )
        except Exception as e:
            raise HTTPException(
                status_code=500, detail=f"Failed to initialize BYOK agents: {str(e)}"
            ) from e
    elif model_override or provider_override or vision_model_override:
        # Server mode with model overrides: Create custom workflow with server's API key
        server_api_key = os.getenv("OPENROUTER_API_KEY")
        if not server_api_key:
            raise HTTPException(
                status_code=503,
                detail="Server not configured for model overrides (missing OPENROUTER_API_KEY)",
            )

        try:
            active_workflow = create_openrouter_workflow(
                api_key=server_api_key,
                annotation_model=model_override,
                annotation_provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id=user_id_override,
                schema_dir=_byok_config.get("schema_dir"),
                validator_path=_byok_config.get("validator_path"),
                use_js_validator=_byok_config.get("use_js_validator", True),
            )
            # Note: Vision agent uses its own provider (deepinfra/fp8 for qwen-vl)
            # Only pass provider_override to vision if a custom vision_model was specified
            vision_provider = provider_override if vision_model_override else None
            active_vision_agent = create_byok_vision_agent(
                server_api_key,
                vision_model=vision_model_override,
                provider=vision_provider,
                temperature=temperature,
                user_id_override=user_id_override,
            )
        except Exception as e:
            raise HTTPException(
                status_code=500,
                detail=f"Failed to initialize workflow with model override: {str(e)}",
            ) from e
    else:
        # Server mode: Use pre-initialized workflow and vision agent
        if workflow is None:
            raise HTTPException(status_code=503, detail="Workflow not initialized")
        if vision_agent is None:
            raise HTTPException(
                status_code=503,
                detail="Vision model not available. Please use OpenRouter provider.",
            )
        active_workflow = workflow
        active_vision_agent = vision_agent

    try:
        start_time = time.time()

        # Step 1: Generate image description using vision model
        vision_result = await active_vision_agent.describe_image(
            image_data=request.image,
            custom_prompt=request.prompt,
        )

        image_description = vision_result["description"]
        image_metadata = vision_result["metadata"]

        # Step 2: Pass description through HED annotation workflow
        config = {"recursion_limit": 100}

        final_state = await active_workflow.run(
            input_description=image_description,
            schema_version=request.schema_version,
            max_validation_attempts=request.max_validation_attempts,
            run_assessment=request.run_assessment,
            config=config,
        )
        latency_ms = int((time.time() - start_time) * 1000)

        # Determine overall status
        is_valid = final_state["is_valid"] and len(final_state["validation_errors"]) == 0
        status = "success" if is_valid else "failed"

        # Collect telemetry if enabled
        if request.telemetry_enabled and telemetry_collector:
            # Get model info from request body, BYOK headers, or server config
            model_name = (
                request.model
                or req.headers.get("x-openrouter-model")
                or os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
            )
            temperature = (
                request.temperature
                or float(req.headers.get("x-openrouter-temperature", 0))
                or _byok_config.get("temperature", 0.1)
            )

            event = TelemetryEvent.create(
                description=image_description,  # Use generated image description
                schema_version=request.schema_version,
                hed_string=final_state["current_annotation"],
                iterations=final_state["validation_attempts"],
                validation_errors=final_state["validation_errors"],
                model=model_name,
                provider=request.provider or req.headers.get("x-openrouter-provider"),
                temperature=temperature,
                latency_ms=latency_ms,
                source="api-image",  # Distinguish from text-based annotation
            )
            await telemetry_collector.collect(event)

        return ImageAnnotationResponse(
            image_description=image_description,
            annotation=final_state["current_annotation"],
            is_valid=is_valid,
            is_faithful=final_state["is_faithful"],
            is_complete=final_state["is_complete"],
            validation_attempts=final_state["validation_attempts"],
            validation_errors=final_state["validation_errors"],
            validation_warnings=final_state["validation_warnings"],
            evaluation_feedback=final_state["evaluation_feedback"],
            assessment_feedback=final_state["assessment_feedback"],
            status=status,
            image_metadata=image_metadata,
        )

    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Image annotation workflow failed: {str(e)}",
        ) from e


@app.post("/annotate/stream")
async def annotate_stream(
    request: AnnotationRequest,
    req: Request,
    api_key: str = Depends(api_key_auth),
):
    """Generate HED annotation with real-time progress updates via Server-Sent Events.

    This endpoint streams progress updates as the workflow runs through different
    stages (annotation, validation, evaluation, assessment), providing real-time
    feedback to the user.

    Supports both server-mode and BYOK (Bring Your Own Key) authentication.

    Args:
        request: Annotation request with description and parameters
        req: FastAPI request to extract headers
        api_key: Authentication result (injected by dependency)

    Returns:
        StreamingResponse with Server-Sent Events

    Raises:
        HTTPException: If workflow fails or authentication fails
    """
    from src.agents.state import create_initial_state

    # Determine which workflow to use (same logic as /annotate)
    model_override = request.model or req.headers.get("x-openrouter-model")
    provider_override = request.provider or req.headers.get("x-openrouter-provider")
    eval_model_override = req.headers.get("x-openrouter-eval-model")
    eval_provider_override = req.headers.get("x-openrouter-eval-provider")
    temp_header = req.headers.get("x-openrouter-temperature")
    temperature = request.temperature
    if temperature is None and temp_header:
        try:
            temperature = float(temp_header)
        except ValueError:
            pass  # Invalid header value, use default temperature
    user_id_override = req.headers.get("x-user-id")

    if api_key == "byok":
        openrouter_key = req.headers.get("x-openrouter-key")
        if not openrouter_key:
            raise HTTPException(status_code=401, detail="Missing X-OpenRouter-Key header")
        try:
            active_workflow = create_byok_workflow(
                openrouter_key,
                model=model_override,
                provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id_override=user_id_override,
            )
        except Exception as e:
            raise HTTPException(
                status_code=500, detail=f"Failed to initialize BYOK workflow: {str(e)}"
            ) from e
    elif model_override or provider_override:
        server_api_key = os.getenv("OPENROUTER_API_KEY")
        if not server_api_key:
            raise HTTPException(
                status_code=503,
                detail="Server not configured for model overrides (missing OPENROUTER_API_KEY)",
            )
        try:
            active_workflow = create_openrouter_workflow(
                api_key=server_api_key,
                annotation_model=model_override,
                annotation_provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id=user_id_override,
                schema_dir=_byok_config.get("schema_dir"),
                validator_path=_byok_config.get("validator_path"),
                use_js_validator=_byok_config.get("use_js_validator", True),
            )
        except Exception as e:
            raise HTTPException(
                status_code=500, detail=f"Failed to initialize workflow: {str(e)}"
            ) from e
    else:
        if workflow is None:
            raise HTTPException(status_code=503, detail="Workflow not initialized")
        active_workflow = workflow

    # Create initial state
    initial_state = create_initial_state(
        request.description,
        request.schema_version,
        request.max_validation_attempts,
        10,  # max_total_iterations
        request.run_assessment,
    )

    # Node name to user-friendly stage mapping
    node_stage_map = {
        "annotate": ("annotating", "Generating HED annotation..."),
        "validate": ("validating", "Validating HED annotation..."),
        "summarize_feedback": ("refining", "Processing validation feedback..."),
        "evaluate": ("evaluating", "Evaluating annotation faithfulness..."),
        "assess": ("assessing", "Running final assessment..."),
    }

    async def event_generator():
        """Generate SSE events for workflow progress using LangGraph streaming."""

        def send_event(event_type: str, data: dict) -> str:
            return f"event: {event_type}\ndata: {json.dumps(data)}\n\n"

        # SSE padding comment to force Safari to open the stream
        yield ": stream opened\n\n"

        try:
            # Send initial start event
            yield send_event(
                "progress", {"stage": "starting", "message": "Initializing annotation workflow..."}
            )

            # Track state and progress
            current_state = initial_state.copy()
            last_stage = None
            validation_attempt = 0

            # Use LangGraph's astream_events for real-time streaming
            config = {"recursion_limit": 100}
            async for event in active_workflow.graph.astream_events(
                initial_state, config=config, version="v2"
            ):
                event_type = event.get("event")
                name = event.get("name", "")

                # Handle node start events
                if event_type == "on_chain_start" and name in node_stage_map:
                    stage, message = node_stage_map[name]

                    # Track validation attempts
                    if name == "validate":
                        validation_attempt += 1

                    # Only send if stage changed
                    if stage != last_stage:
                        last_stage = stage
                        progress_data = {
                            "stage": stage,
                            "message": message,
                        }
                        if name == "validate":
                            progress_data["attempt"] = validation_attempt
                        yield send_event("progress", progress_data)

                # Handle node end events to get intermediate state
                if event_type == "on_chain_end" and name in node_stage_map:
                    output = event.get("data", {}).get("output", {})
                    if isinstance(output, dict):
                        current_state.update(output)

                        # Send validation result events
                        if name == "validate":
                            is_valid = output.get("is_valid", False)
                            errors = output.get("validation_errors", [])
                            if is_valid:
                                yield send_event(
                                    "validation",
                                    {
                                        "valid": True,
                                        "attempt": validation_attempt,
                                        "message": "Validation passed",
                                    },
                                )
                            elif errors:
                                tag_suggestions = output.get("tag_suggestions", {})
                                validation_data = {
                                    "valid": False,
                                    "attempt": validation_attempt,
                                    "errors": errors[:3],  # Send first 3 errors
                                    "message": f"Found {len(errors)} validation error(s)",
                                }
                                if tag_suggestions:
                                    validation_data["tag_suggestions"] = tag_suggestions
                                yield send_event("validation", validation_data)

            # Send final result
            is_valid = (
                current_state.get("is_valid", False)
                and len(current_state.get("validation_errors", [])) == 0
            )
            status = "success" if is_valid else "failed"
            result = {
                "annotation": current_state.get("current_annotation", ""),
                "is_valid": is_valid,
                "is_faithful": current_state.get("is_faithful", False),
                "is_complete": current_state.get("is_complete", False),
                "validation_attempts": current_state.get("validation_attempts", 0),
                "validation_errors": current_state.get("validation_errors", []),
                "validation_warnings": current_state.get("validation_warnings", []),
                "tag_suggestions": current_state.get("tag_suggestions", {}),
                "evaluation_feedback": current_state.get("evaluation_feedback", ""),
                "assessment_feedback": current_state.get("assessment_feedback", ""),
                "status": status,
            }

            yield send_event("result", result)
            yield send_event("done", {"message": "Workflow completed"})

        except asyncio.CancelledError:
            raise
        except Exception:
            # Log the actual error for debugging, but return a generic message
            logging.exception("Streaming workflow error")
            yield send_event("error", {"message": "An error occurred during annotation processing"})
            yield send_event("done", {"message": "Workflow ended with error"})

    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",  # Disable nginx buffering
            "X-Content-Type-Options": "nosniff",  # Prevent MIME-type sniffing; helps Safari trust text/event-stream
        },
    )


@app.post("/annotate-from-image/stream")
async def annotate_from_image_stream(
    request: ImageAnnotationRequest,
    req: Request,
    api_key: str = Depends(api_key_auth),
):
    """Generate HED annotation from an image with real-time progress updates via Server-Sent Events.

    This endpoint streams progress updates as the workflow runs through different
    stages (vision, annotation, validation, evaluation, assessment), providing real-time
    feedback to the user.

    Supports both server-mode and BYOK (Bring Your Own Key) authentication.

    Args:
        request: Image annotation request with base64 image and parameters
        req: FastAPI request to extract headers
        api_key: Authentication result (injected by dependency)

    Returns:
        StreamingResponse with Server-Sent Events

    Raises:
        HTTPException: If workflow or vision agent fails or authentication fails
    """
    from src.agents.state import create_initial_state

    # Determine which workflow and vision agent to use (same logic as /annotate-from-image)
    model_override = request.model or req.headers.get("x-openrouter-model")
    vision_model_override = request.vision_model or req.headers.get("x-openrouter-vision-model")
    provider_override = request.provider or req.headers.get("x-openrouter-provider")
    eval_model_override = req.headers.get("x-openrouter-eval-model")
    eval_provider_override = req.headers.get("x-openrouter-eval-provider")
    temp_header = req.headers.get("x-openrouter-temperature")
    temperature = request.temperature
    if temperature is None and temp_header:
        try:
            temperature = float(temp_header)
        except ValueError:
            pass
    user_id_override = req.headers.get("x-user-id")

    if api_key == "byok":
        openrouter_key = req.headers.get("x-openrouter-key")
        if not openrouter_key:
            raise HTTPException(status_code=401, detail="Missing X-OpenRouter-Key header")
        try:
            active_workflow = create_byok_workflow(
                openrouter_key,
                model=model_override,
                provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id_override=user_id_override,
            )
            active_vision_agent = create_byok_vision_agent(
                openrouter_key,
                vision_model=vision_model_override,
                provider=provider_override,
                temperature=temperature,
                user_id_override=user_id_override,
            )
        except Exception as e:
            raise HTTPException(
                status_code=500, detail=f"Failed to initialize BYOK agents: {str(e)}"
            ) from e
    elif model_override or provider_override or vision_model_override:
        server_api_key = os.getenv("OPENROUTER_API_KEY")
        if not server_api_key:
            raise HTTPException(
                status_code=503,
                detail="Server not configured for model overrides (missing OPENROUTER_API_KEY)",
            )
        try:
            active_workflow = create_openrouter_workflow(
                api_key=server_api_key,
                annotation_model=model_override,
                annotation_provider=provider_override,
                eval_model=eval_model_override,
                eval_provider=eval_provider_override,
                temperature=temperature,
                user_id=user_id_override,
                schema_dir=_byok_config.get("schema_dir"),
                validator_path=_byok_config.get("validator_path"),
                use_js_validator=_byok_config.get("use_js_validator", True),
            )
            # Note: Vision agent uses its own provider (deepinfra/fp8 for qwen-vl)
            # Only pass provider_override to vision if a custom vision_model was specified
            vision_provider = provider_override if vision_model_override else None
            active_vision_agent = create_byok_vision_agent(
                server_api_key,
                vision_model=vision_model_override,
                provider=vision_provider,
                temperature=temperature,
                user_id_override=user_id_override,
            )
        except Exception as e:
            raise HTTPException(
                status_code=500, detail=f"Failed to initialize workflow: {str(e)}"
            ) from e
    else:
        if workflow is None:
            raise HTTPException(status_code=503, detail="Workflow not initialized")
        if vision_agent is None:
            raise HTTPException(
                status_code=503,
                detail="Vision model not available. Please use OpenRouter provider.",
            )
        active_workflow = workflow
        active_vision_agent = vision_agent

    # Node name to user-friendly stage mapping
    node_stage_map = {
        "annotate": ("annotating", "Generating HED annotation..."),
        "validate": ("validating", "Validating HED annotation..."),
        "summarize_feedback": ("refining", "Processing validation feedback..."),
        "evaluate": ("evaluating", "Evaluating annotation faithfulness..."),
        "assess": ("assessing", "Running final assessment..."),
    }

    async def event_generator():
        """Generate SSE events for image annotation workflow progress."""

        def send_event(event_type: str, data: dict) -> str:
            return f"event: {event_type}\ndata: {json.dumps(data)}\n\n"

        # SSE padding comment to force Safari to open the stream
        yield ": stream opened\n\n"

        try:
            # Send initial start event
            yield send_event(
                "progress", {"stage": "starting", "message": "Initializing image annotation..."}
            )

            # Step 1: Generate image description using vision model
            yield send_event(
                "progress", {"stage": "vision", "message": "Analyzing image with vision model..."}
            )

            vision_result = await active_vision_agent.describe_image(
                image_data=request.image,
                custom_prompt=request.prompt,
            )

            image_description = vision_result["description"]
            image_metadata = vision_result["metadata"]

            # Send image description event
            yield send_event(
                "image_description",
                {"description": image_description, "metadata": image_metadata},
            )

            # Step 2: Create initial state for annotation workflow
            initial_state = create_initial_state(
                image_description,
                request.schema_version,
                request.max_validation_attempts,
                10,  # max_total_iterations
                request.run_assessment,
            )

            # Track state and progress
            current_state = initial_state.copy()
            last_stage = None
            validation_attempt = 0

            # Use LangGraph's astream_events for real-time streaming
            config = {"recursion_limit": 100}
            async for event in active_workflow.graph.astream_events(
                initial_state, config=config, version="v2"
            ):
                event_type = event.get("event")
                name = event.get("name", "")

                # Handle node start events
                if event_type == "on_chain_start" and name in node_stage_map:
                    stage, message = node_stage_map[name]

                    # Track validation attempts
                    if name == "validate":
                        validation_attempt += 1

                    # Only send if stage changed
                    if stage != last_stage:
                        last_stage = stage
                        progress_data = {
                            "stage": stage,
                            "message": message,
                        }
                        if name == "validate":
                            progress_data["attempt"] = validation_attempt
                        yield send_event("progress", progress_data)

                # Handle node end events to get intermediate state
                if event_type == "on_chain_end" and name in node_stage_map:
                    output = event.get("data", {}).get("output", {})
                    if isinstance(output, dict):
                        current_state.update(output)

                        # Send validation result events
                        if name == "validate":
                            is_valid = output.get("is_valid", False)
                            errors = output.get("validation_errors", [])
                            if is_valid:
                                yield send_event(
                                    "validation",
                                    {
                                        "valid": True,
                                        "attempt": validation_attempt,
                                        "message": "Validation passed",
                                    },
                                )
                            elif errors:
                                tag_suggestions = output.get("tag_suggestions", {})
                                validation_data = {
                                    "valid": False,
                                    "attempt": validation_attempt,
                                    "errors": errors[:3],  # Send first 3 errors
                                    "message": f"Found {len(errors)} validation error(s)",
                                }
                                if tag_suggestions:
                                    validation_data["tag_suggestions"] = tag_suggestions
                                yield send_event("validation", validation_data)

            # Send final result
            is_valid = (
                current_state.get("is_valid", False)
                and len(current_state.get("validation_errors", [])) == 0
            )
            status = "success" if is_valid else "failed"
            result = {
                "image_description": image_description,
                "annotation": current_state.get("current_annotation", ""),
                "is_valid": is_valid,
                "is_faithful": current_state.get("is_faithful", False),
                "is_complete": current_state.get("is_complete", False),
                "validation_attempts": current_state.get("validation_attempts", 0),
                "validation_errors": current_state.get("validation_errors", []),
                "validation_warnings": current_state.get("validation_warnings", []),
                "tag_suggestions": current_state.get("tag_suggestions", {}),
                "evaluation_feedback": current_state.get("evaluation_feedback", ""),
                "assessment_feedback": current_state.get("assessment_feedback", ""),
                "status": status,
                "image_metadata": image_metadata,
            }

            yield send_event("result", result)
            yield send_event("done", {"message": "Workflow completed"})

        except asyncio.CancelledError:
            raise
        except Exception:
            # Log the actual error for debugging, but return a generic message
            logging.exception("Streaming image annotation workflow error")
            yield send_event("error", {"message": "An error occurred during image annotation"})
            yield send_event("done", {"message": "Workflow ended with error"})

    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",  # Disable nginx buffering
            "X-Content-Type-Options": "nosniff",  # Prevent MIME-type sniffing; helps Safari trust text/event-stream
        },
    )


@app.post("/validate", response_model=ValidationResponse)
async def validate(
    request: ValidationRequest, api_key: str = Depends(api_key_auth)
) -> ValidationResponse:
    """Validate a HED annotation string.

    Requires API key authentication via X-API-Key header.

    Args:
        request: Validation request with HED string
        api_key: API key for authentication (injected by dependency)

    Returns:
        Validation result with errors and warnings

    Raises:
        HTTPException: If validation fails or authentication fails
    """
    if schema_loader is None:
        raise HTTPException(status_code=503, detail="Schema loader not initialized")

    try:
        # Load schema
        schema = schema_loader.load_schema(request.schema_version)

        # Validate using Python validator
        validator = HedPythonValidator(schema)
        result = validator.validate(request.hed_string)

        return ValidationResponse(
            is_valid=result.is_valid,
            errors=[f"[{e.code}] {e.message}" for e in result.errors],
            warnings=[f"[{w.code}] {w.message}" for w in result.warnings],
            parsed_string=result.parsed_string,
        )

    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Validation failed: {str(e)}",
        ) from e


@app.post("/feedback", response_model=FeedbackResponse)
async def submit_feedback(request: FeedbackRequest) -> FeedbackResponse:
    """Submit user feedback about an annotation.

    This endpoint is public (no authentication required) to allow feedback
    from frontend and CLI users without requiring API keys.

    The feedback is saved and optionally processed immediately if GITHUB_TOKEN
    is available in the environment. Otherwise, feedback is saved for later
    processing via CI workflow.

    Args:
        request: Feedback submission with annotation data and user comment

    Returns:
        FeedbackResponse with feedback ID and status
    """
    from datetime import datetime
    from uuid import uuid4

    try:
        # Generate unique feedback ID
        feedback_id = str(uuid4())[:8]
        timestamp = datetime.now().isoformat()

        # Create feedback record
        feedback_record = {
            "feedback_id": feedback_id,
            "timestamp": timestamp,
            "version": __version__,
            "type": request.type,
            "description": request.description,
            "image_description": request.image_description,
            "annotation": request.annotation,
            "is_valid": request.is_valid,
            "is_faithful": request.is_faithful,
            "is_complete": request.is_complete,
            "validation_errors": request.validation_errors,
            "validation_warnings": request.validation_warnings,
            "evaluation_feedback": request.evaluation_feedback,
            "assessment_feedback": request.assessment_feedback,
            "user_comment": request.user_comment,
        }

        # Save to feedback/unprocessed directory (always save for backup/audit)
        feedback_dir = Path("feedback/unprocessed")
        feedback_dir.mkdir(parents=True, exist_ok=True)

        filename = f"feedback-{timestamp.replace(':', '-').replace('.', '-')}.jsonl"
        filepath = feedback_dir / filename

        with open(filepath, "w") as f:
            f.write(json.dumps(feedback_record) + "\n")

        # Log the feedback submission
        audit_logger.log(
            event="feedback_submitted",
            data={"feedback_id": feedback_id, "type": request.type},
        )

        # Try to process immediately if GitHub token and OpenRouter key are available
        # Use OPENROUTER_API_KEY_FOR_TESTING to track feedback processing costs separately
        processing_result = None
        github_token = os.getenv("GITHUB_TOKEN")
        openrouter_key = os.getenv("OPENROUTER_API_KEY_FOR_TESTING") or os.getenv(
            "OPENROUTER_API_KEY"
        )

        if github_token and openrouter_key:
            try:
                from src.agents.feedback_triage_agent import (
                    FeedbackRecord as FeedbackRecordModel,
                )
                from src.agents.feedback_triage_agent import (
                    FeedbackTriageAgent,
                    save_processed_feedback,
                )
                from src.utils.github_client import GitHubClient

                # Create feedback record model
                record = FeedbackRecordModel.from_json(feedback_record)

                # Create GitHub client
                github_client = GitHubClient(
                    token=github_token,
                    owner=os.getenv("GITHUB_REPOSITORY_OWNER", "Annotation-Garden"),
                    repo=os.getenv("GITHUB_REPOSITORY", "hedit").split("/")[-1],
                )

                # Create LLM for triage
                model = os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
                provider = os.getenv("LLM_PROVIDER_PREFERENCE", "")
                llm = create_openrouter_llm(
                    model=model,
                    api_key=openrouter_key,
                    temperature=0.1,
                    max_tokens=1000,
                    provider=provider if provider else None,
                )

                # Create and run triage agent
                agent = FeedbackTriageAgent(llm=llm, github_client=github_client)
                processing_result = await agent.process_and_execute(record, dry_run=False)

                # Save processed result
                save_processed_feedback(record, processing_result, Path("feedback/processed"))

                # Remove the original feedback file since it's been processed
                filepath.unlink(missing_ok=True)

                audit_logger.log(
                    event="feedback_processed",
                    data={
                        "feedback_id": feedback_id,
                        "action": processing_result.get("action"),
                        "issue_number": processing_result.get("issue_number"),
                    },
                )

            except Exception as e:
                # Log error but don't fail the request - feedback is still saved
                audit_logger.log(
                    event="feedback_processing_error",
                    data={"feedback_id": feedback_id, "error": str(e)},
                )

        # Build response message
        if processing_result:
            action = processing_result.get("action", "unknown")
            if action == "create_issue":
                message = f"Thank you! Your feedback has been submitted as issue #{processing_result.get('issue_number')}."
            elif action == "comment":
                message = f"Thank you! Your feedback has been added to existing issue #{processing_result.get('issue_number')}."
            else:
                message = "Thank you for your feedback! It has been archived for review."
        else:
            message = "Thank you for your feedback! It will be reviewed and processed."

        return FeedbackResponse(
            success=True,
            feedback_id=feedback_id,
            message=message,
        )

    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Failed to save feedback: {str(e)}",
        ) from e


@app.get("/version")
async def get_version():
    """Get API version information.

    Returns:
        Version information including commit hash for deployment verification
    """
    return {
        "version": __version__,
        "commit": os.getenv("GIT_COMMIT", "unknown"),
    }


@app.get("/")
async def root():
    """Root endpoint with API information.

    Returns:
        API information
    """
    return {
        "name": "HEDit API",
        "version": __version__,
        "description": "Multi-agent system for HED annotation generation",
        "endpoints": {
            "POST /annotate": "Generate HED annotation from description",
            "POST /annotate/stream": "Generate HED annotation with streaming progress",
            "POST /annotate-from-image": "Generate HED annotation from image",
            "POST /annotate-from-image/stream": "Generate HED annotation from image with streaming",
            "POST /validate": "Validate HED annotation string",
            "POST /feedback": "Submit user feedback about annotation",
            "GET /health": "Health check",
            "GET /version": "Get version information",
        },
    }


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "src.api.main:app",
        host="0.0.0.0",
        port=38427,
        reload=True,
    )
