"""Schema runtime introspection endpoint."""

import asyncio
import time

from fastapi import APIRouter, Depends, HTTPException, Path, status
from sqlalchemy.orm import Session

from robosystems.database import get_async_db_session
from robosystems.logger import logger
from robosystems.middleware.auth.dependencies import get_current_user_with_graph
from robosystems.middleware.graph import get_universal_repository
from robosystems.middleware.graph.types import GRAPH_OR_SUBGRAPH_ID_PATTERN
from robosystems.middleware.otel.metrics import (
  endpoint_metrics_decorator,
  get_endpoint_metrics,
)
from robosystems.middleware.rate_limits import subscription_aware_rate_limit_dependency
from robosystems.middleware.robustness import (
  OperationStatus,
  OperationType,
  get_operation_logger,
  record_operation_metric,
)
from robosystems.models.api.common import RESOURCE_ERROR_RESPONSES
from robosystems.models.api.graphs.schema import SchemaInfoResponse
from robosystems.models.core import User

from .utils import circuit_breaker, get_schema_info, timeout_coordinator

router = APIRouter()


@router.get(
  "/schema",
  response_model=SchemaInfoResponse,
  summary="Get Runtime Graph Schema",
  description="Runtime schema from actual graph data — node labels, relationship types, and up to 10 sample properties per node type. For the original schema definition or structured export formats, use `/schema/export`.",
  operation_id="getGraphSchema",
  responses={
    **RESOURCE_ERROR_RESPONSES,
    504: {"description": "Schema operation timed out"},
  },
)
@endpoint_metrics_decorator(
  "/v1/graphs/{graph_id}/schema", business_event_type="schema_retrieved"
)
async def get_graph_schema_info(
  graph_id: str = Path(
    ...,
    description="The graph database to get schema for",
    pattern=GRAPH_OR_SUBGRAPH_ID_PATTERN,
  ),
  current_user: User = Depends(get_current_user_with_graph),
  session: Session = Depends(get_async_db_session),
  _: None = Depends(subscription_aware_rate_limit_dependency),
) -> SchemaInfoResponse:
  # Initialize robustness components
  operation_logger = get_operation_logger()

  # Record operation start and get timing
  operation_start_time = time.time()

  # Record operation start metrics
  record_operation_metric(
    operation_type=OperationType.SCHEMA_OPERATION,
    status=OperationStatus.SUCCESS,  # Will be updated on completion
    duration_ms=0.0,  # Will be updated on completion
    endpoint="/v1/graphs/{graph_id}/schema",
    graph_id=graph_id,
    user_id=current_user.id,
    operation_name="get_schema",
    metadata={},
  )

  # Initialize timeout for error handling
  operation_timeout = None

  try:
    # Check circuit breaker before processing
    circuit_breaker.check_circuit(graph_id, "schema")

    # Set up timeout coordination for schema operations
    operation_timeout = timeout_coordinator.calculate_timeout(
      operation_type="database_query",
      complexity_factors={
        "operation": "schema_introspection",
        "expected_complexity": "medium",
      },
    )
    # Schema operations are included - no credit consumption

    # Log the request with operation logger
    operation_logger.log_external_service_call(
      endpoint="/v1/graphs/{graph_id}/schema",
      service_name="graph_repository",
      operation="get_schema",
      duration_ms=0.0,  # Will be updated on completion
      status="processing",
      graph_id=graph_id,
      user_id=current_user.id,
      metadata={},
    )

    # Get repository with unified authentication and authorization
    repository = await get_universal_repository(graph_id, "read")

    # Get schema information with timeout coordination
    schema = await asyncio.wait_for(
      get_schema_info(repository), timeout=operation_timeout
    )

    # Record business event for successful schema retrieval
    metrics_instance = get_endpoint_metrics()
    metrics_instance.record_business_event(
      endpoint="/v1/graphs/{graph_id}/schema",
      method="GET",
      event_type="schema_retrieved_successfully",
      event_data={
        "graph_id": graph_id,
        "node_labels_count": len(schema.get("node_labels", [])),
        "relationship_types_count": len(schema.get("relationship_types", [])),
        "node_properties_count": len(schema.get("node_properties", {})),
      },
      user_id=current_user.id,
    )

    # Record successful operation
    operation_duration_ms = (time.time() - operation_start_time) * 1000
    circuit_breaker.record_success(graph_id, "schema")

    # Record success metrics
    record_operation_metric(
      operation_type=OperationType.SCHEMA_OPERATION,
      status=OperationStatus.SUCCESS,
      duration_ms=operation_duration_ms,
      endpoint="/v1/graphs/{graph_id}/schema",
      graph_id=graph_id,
      user_id=current_user.id,
      operation_name="get_schema",
      metadata={
        "node_labels_count": len(schema.get("node_labels", [])),
        "relationship_types_count": len(schema.get("relationship_types", [])),
      },
    )

    return SchemaInfoResponse(graph_id=graph_id, schema=schema)

  except TimeoutError:
    # Record circuit breaker failure and timeout metrics
    circuit_breaker.record_failure(graph_id, "schema")
    operation_duration_ms = (time.time() - operation_start_time) * 1000

    # Record timeout failure metrics
    record_operation_metric(
      operation_type=OperationType.SCHEMA_OPERATION,
      status=OperationStatus.FAILURE,
      duration_ms=operation_duration_ms,
      endpoint="/v1/graphs/{graph_id}/schema",
      graph_id=graph_id,
      user_id=current_user.id,
      operation_name="get_schema",
      metadata={
        "error_type": "timeout",
        "timeout_seconds": operation_timeout,
      },
    )

    timeout_str = f" after {operation_timeout}s" if operation_timeout else ""
    logger.error(f"Schema operation timeout{timeout_str} for user {current_user.id}")
    raise HTTPException(
      status_code=status.HTTP_504_GATEWAY_TIMEOUT,
      detail="Schema operation timed out",
    )
  except HTTPException:
    # Record circuit breaker failure for HTTP exceptions
    circuit_breaker.record_failure(graph_id, "schema")
    operation_duration_ms = (time.time() - operation_start_time) * 1000

    # Record failure metrics
    record_operation_metric(
      operation_type=OperationType.SCHEMA_OPERATION,
      status=OperationStatus.FAILURE,
      duration_ms=operation_duration_ms,
      endpoint="/v1/graphs/{graph_id}/schema",
      graph_id=graph_id,
      user_id=current_user.id,
      operation_name="get_schema",
      metadata={
        "error_type": "http_exception",
      },
    )
    raise
  except Exception as e:
    # Record circuit breaker failure for general exceptions
    circuit_breaker.record_failure(graph_id, "schema")
    operation_duration_ms = (time.time() - operation_start_time) * 1000

    # Record failure metrics
    record_operation_metric(
      operation_type=OperationType.SCHEMA_OPERATION,
      status=OperationStatus.FAILURE,
      duration_ms=operation_duration_ms,
      endpoint="/v1/graphs/{graph_id}/schema",
      graph_id=graph_id,
      user_id=current_user.id,
      operation_name="get_schema",
      metadata={
        "error_type": type(e).__name__,
        "error_message": str(e),
      },
    )

    # Record business event for unexpected errors
    metrics_instance = get_endpoint_metrics()
    metrics_instance.record_business_event(
      endpoint="/v1/graphs/{graph_id}/schema",
      method="GET",
      event_type="schema_retrieval_unexpected_error",
      event_data={
        "graph_id": graph_id,
        "error_type": type(e).__name__,
        "error_message": str(e),
      },
      user_id=current_user.id,
    )
    logger.error(f"Error getting graph schema: {e!s}")
    raise HTTPException(
      status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
      detail="Failed to retrieve graph schema",
    )
