from typing import Any

from flask import jsonify, request
from pydantic import ValidationError as PydanticValidationError
from werkzeug.exceptions import HTTPException

from semantic_models.exceptions import ConfigurationError, NotFoundError, ValidationError
from semantic_models.glossary_types import GlossaryBinding
from semantic_models.schemas import (
    BindingTargetType,
    GlossaryBindingResponse,
    GoldenQueryResponse,
    SemanticModelSpec,
)
from semantic_models.services.factory import get_semantic_models_service
from semantic_models.services.semantic_models_service import (
    GoldenQuery,
    ModelWithVersions,
    SemanticModelsService,
    Version,
)
from semantic_models.validation.expressions import (
    validate_attribute_expression,
    validate_filter_expression,
    validate_metric_expression,
)

# Re-export validation utilities for backwards compatibility
from semantic_models.utils.sql_safety import (
    IDENTIFIER_RE,
    PATH_COMPONENT_RE,
    escape_sql_string,
    safe_filename,
    safe_identifier,
    safe_path_component,
)
from semantic_models.utils.spec_utils import build_spec_from_version

__all__ = [
    "IDENTIFIER_RE",
    "PATH_COMPONENT_RE",
    "escape_sql_string",
    "safe_filename",
    "safe_identifier",
    "safe_path_component",
    "binding_to_response",
    "get_current_user",
    "get_service",
    "golden_query_to_response",
    "build_spec_from_version",
    "model_to_response",
    "handle_error",
    "sanitize_spec_relationships",
    "validate_spec_expressions",
    "version_to_response",
]


def get_current_user() -> str | None:
    import dataiku

    headers = dict(request.headers)
    auth = dataiku.api_client().get_auth_info_from_browser_headers(headers)
    return auth["authIdentifier"]


def get_service() -> SemanticModelsService:
    """Get the CRUD service instance."""
    return get_semantic_models_service()


def binding_to_response(binding: GlossaryBinding, spec: SemanticModelSpec) -> GlossaryBindingResponse:
    """Convert a GlossaryBinding dataclass to a response schema with validation."""
    return GlossaryBindingResponse(
        termId=binding.termId,
        targetEntityClass=binding.targetEntityClass,
        targetType=BindingTargetType(binding.targetType),
        targetName=binding.targetName,
        created=binding.created,
    )


def golden_query_to_response(gq: GoldenQuery) -> GoldenQueryResponse:
    """Convert a GoldenQuery to a GoldenQueryResponse."""
    return GoldenQueryResponse(
        name=gq.name,
        description=gq.description,
        question=gq.question,
        generatedSql=gq.generatedSql,
        created=gq.created,
    )


def version_to_response(service: SemanticModelsService, version: Version, include_entities: bool = True) -> dict:
    """Convert a Version to a response dict."""
    data = service._version_to_dict(version)
    if not include_entities:
        data.pop("entities", None)
        data.pop("relationships", None)
    return data


def model_to_response(
    service: SemanticModelsService, model_with_versions: ModelWithVersions, include_entities: bool = True
) -> dict:
    """Convert a ModelWithVersions to a response dict."""
    model = model_with_versions.model
    return {
        "id": model.id,
        "projectKey": model.projectKey,
        "name": model.name,
        "activeVersionId": model.activeVersionId,
        "versionTag": model.versionTag,
        "creationTag": model.creationTag,
        "tags": model.tags,
        "customFields": model.customFields,
        "checklists": model.checklists,
        "description": model.description,
        "shortDesc": model.shortDesc,
        "versions": [version_to_response(service, v, include_entities=include_entities) for v in model_with_versions.versions],
    }


def validate_spec_expressions(spec: SemanticModelSpec, skip_empty: bool = True) -> list[str]:
    errors: list[str] = []

    for entity in spec.entities:
        # Validate attribute expressions
        for attr in entity.attributes:
            expr = attr.pseudoSQLExpression if attr.type == "EXPRESSION" else attr.column
            if skip_empty and not (expr and str(expr).strip()):
                continue
            is_valid, error = validate_attribute_expression(str(expr))
            if not is_valid:
                errors.append(f"Entity '{entity.name}', attribute '{attr.name}': {error}")

        # Validate metric expressions
        for metric in entity.metrics:
            if skip_empty and not (metric.pseudoSQLExpression and metric.pseudoSQLExpression.strip()):
                continue
            is_valid, error = validate_metric_expression(metric.pseudoSQLExpression)
            if not is_valid:
                errors.append(f"Entity '{entity.name}', metric '{metric.name}': {error}")

        # Validate filter expressions
        for filter_ in entity.filters:
            if skip_empty and not (filter_.pseudoSQLExpression and filter_.pseudoSQLExpression.strip()):
                continue
            is_valid, error = validate_filter_expression(filter_.pseudoSQLExpression)
            if not is_valid:
                errors.append(f"Entity '{entity.name}', filter '{filter_.name}': {error}")

    return errors


def sanitize_spec_relationships(spec: SemanticModelSpec) -> list[str]:
    """Drop relationships that reference unknown entities.

    Returns a list of dropped relationship summaries.
    """
    if not spec or not spec.relationships:
        return []

    entity_ids = {e.name for e in spec.entities if isinstance(e.name, str)}
    if not entity_ids:
        dropped = [f"{r.firstEntity} <-> {r.secondEntity}" for r in spec.relationships]
        spec.relationships = []
        return dropped

    kept = []
    dropped = []
    for rel in spec.relationships:
        left = rel.firstEntity
        right = rel.secondEntity
        if left in entity_ids and right in entity_ids:
            kept.append(rel)
        else:
            dropped.append(f"{left} <-> {right}")

    if dropped:
        spec.relationships = kept

    return dropped


def handle_error(e: Exception) -> tuple[Any, int]:
    if isinstance(e, HTTPException):
        return jsonify({"error": e.description}), e.code

    if isinstance(e, NotFoundError):
        return jsonify({"error": str(e)}), 404

    if isinstance(e, ConfigurationError):
        return jsonify({"error": str(e)}), 400

    if isinstance(e, (ValidationError, PydanticValidationError)):
        return jsonify({"error": str(e)}), 400

    if isinstance(e, ValueError):
        return jsonify({"error": str(e)}), 400

    # Log unexpected errors
    import logging

    logging.exception("Unexpected error occurred")

    # Generic internal error
    return jsonify({"error": "Internal server error"}), 500
