import logging
from datetime import datetime, timezone

from flask import Blueprint, jsonify, request

from semantic_models.exceptions import ValidationError
from semantic_models.routes.common import (
    build_spec_from_version,
    get_current_user,
    get_service,
    model_to_response,
    sanitize_spec_relationships,
    validate_spec_expressions,
    version_to_response,
)
from semantic_models.schemas import (
    SemanticModelListItem,
    SemanticModelUpdate,
    SetActiveVersion,
)
from semantic_models.services.semantic_models_service import Model
from semantic_models.logging_utils import summarize_for_logging

bp = Blueprint("semantic_models", __name__, url_prefix="/api")
logger = logging.getLogger(__name__)


def _parse_include_spec(default: bool = False) -> bool:
    value = request.args.get("include_spec")
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes"}


def _model_to_list_item(model: Model) -> SemanticModelListItem:
    return SemanticModelListItem(
        id=model.id,
        projectKey=model.projectKey,
        name=model.name,
        description=model.description,
        shortDesc=model.shortDesc,
        activeVersionId=model.activeVersionId,
        versionTag=model.versionTag,
        creationTag=model.creationTag,
        tags=model.tags,
        customFields=model.customFields,
        checklists=model.checklists,
    )


@bp.route("/semantic-models", methods=["GET"])
def list_models():
    service = get_service()
    models = service.list_models()

    result = []
    for model in models:
        result.append(_model_to_list_item(model).model_dump())

    logger.info("Listed semantic models: count=%d", len(result))

    return jsonify(result)


@bp.route("/semantic-models/<model_id>", methods=["GET"])
def get_model(model_id: str):
    service = get_service()
    model_with_versions = service.get_model(model_id)
    include_spec = _parse_include_spec()
    logger.info(
        "Fetched semantic model: model_id=%s include_spec=%s versions=%d",
        model_id,
        include_spec,
        len(model_with_versions.versions),
    )
    return jsonify(
        model_to_response(service, model_with_versions, include_entities=include_spec)
    )


@bp.route("/semantic-models/<model_id>", methods=["PUT"])
def update_model(model_id: str):
    data = SemanticModelUpdate.model_validate(request.json)
    logger.info(
        "Updating semantic model: model_id=%s payload=%s",
        model_id,
        summarize_for_logging(
            {"name": data.name, "description": data.description, "shortDesc": data.shortDesc},
            max_chars=300,
        ),
    )

    service = get_service()
    model = service.update_model(
        model_id=model_id,
        name=data.name,
        description=data.description,
        short_desc=data.shortDesc,
    )

    model_with_versions = service.get_model(model_id)
    logger.info("Updated semantic model: model_id=%s versions=%d", model_id, len(model_with_versions.versions))
    return jsonify(model_to_response(service, model_with_versions))


@bp.route("/semantic-models/<model_id>", methods=["DELETE"])
def delete_model(model_id: str):
    service = get_service()
    logger.info("Deleting semantic model: model_id=%s", model_id)
    service.delete_model(model_id)
    logger.info("Deleted semantic model: model_id=%s", model_id)
    return "", 204


@bp.route("/semantic-models/<model_id>/versions", methods=["GET"])
def list_versions(model_id: str):
    service = get_service()
    versions = service.list_versions(model_id)
    include_spec = _parse_include_spec()
    logger.info(
        "Listed model versions: model_id=%s include_spec=%s count=%d",
        model_id,
        include_spec,
        len(versions),
    )
    return jsonify(
        [version_to_response(service, v, include_entities=include_spec) for v in versions]
    )


@bp.route("/semantic-models/<model_id>/versions", methods=["POST"])
def create_version(model_id: str):
    data = request.json or {}
    user = get_current_user()
    logger.info(
        "Creating version: model_id=%s from_version=%s payload_keys=%s",
        model_id,
        data.get("fromVersion"),
        sorted(list(data.keys())),
    )

    service = get_service()
    from_version = data.pop("fromVersion", None)

    if "entities" in data or "relationships" in data:
        spec = build_spec_from_version(data)
        dropped_relationships = sanitize_spec_relationships(spec)
        if dropped_relationships:
            logging.info(
                "Dropped %d relationships with unknown entities: %s",
                len(dropped_relationships),
                dropped_relationships[:5],
            )
            data["relationships"] = [r.model_dump() for r in spec.relationships]
        if spec.entities:
            validation_errors = validate_spec_expressions(spec)
            if validation_errors:
                raise ValidationError("Expression validation failed:\n" + "\n".join(validation_errors))

    version = service.create_version(
        model_id=model_id,
        version_data=data,
        created_by=user,
        from_version=from_version,
    )

    logger.info(
        "Created version: model_id=%s version_id=%s entity_count=%d relationship_count=%d",
        model_id,
        version.id,
        len(version.entities),
        len(version.relationships),
    )

    return jsonify(version_to_response(service, version)), 201


@bp.route("/semantic-models/<model_id>/versions/<version_id>", methods=["GET"])
def get_version(model_id: str, version_id: str):
    service = get_service()
    version = service.get_version(model_id, version_id)
    logger.info(
        "Fetched version: model_id=%s version_id=%s entity_count=%d relationship_count=%d",
        model_id,
        version_id,
        len(version.entities),
        len(version.relationships),
    )
    return jsonify(version_to_response(service, version))


@bp.route("/semantic-models/<model_id>/versions/<version_id>", methods=["PUT"])
def update_version(model_id: str, version_id: str):
    data = request.json or {}
    logger.info(
        "Updating version: model_id=%s version_id=%s patch_keys=%s",
        model_id,
        version_id,
        sorted(list(data.keys())),
    )

    if "entities" in data or "relationships" in data:
        spec = build_spec_from_version(data)
        dropped_relationships = sanitize_spec_relationships(spec)
        if dropped_relationships:
            logging.info(
                "Dropped %d relationships with unknown entities: %s",
                len(dropped_relationships),
                dropped_relationships[:5],
            )
            data["relationships"] = [r.model_dump() for r in spec.relationships]
        if spec.entities:
            validation_errors = validate_spec_expressions(spec)
            if validation_errors:
                raise ValidationError("Expression validation failed:\n" + "\n".join(validation_errors))

    private = data.get("privateEditorData")
    if not isinstance(private, dict):
        private = {}
    private["lastSavedAt"] = datetime.now(timezone.utc).isoformat()
    data["privateEditorData"] = private

    service = get_service()
    version = service.update_version(
        model_id=model_id,
        version_id=version_id,
        version_patch=data,
    )

    logger.info(
        "Updated version: model_id=%s version_id=%s entity_count=%d relationship_count=%d",
        model_id,
        version_id,
        len(version.entities),
        len(version.relationships),
    )

    return jsonify(version_to_response(service, version))


@bp.route("/semantic-models/<model_id>/versions/<version_id>", methods=["DELETE"])
def delete_version(model_id: str, version_id: str):
    service = get_service()
    logger.info("Deleting version: model_id=%s version_id=%s", model_id, version_id)

    service.delete_version(model_id, version_id)
    logger.info("Deleted version: model_id=%s version_id=%s", model_id, version_id)
    return "", 204


@bp.route("/semantic-models/<model_id>/active", methods=["PUT"])
def set_active_version(model_id: str):
    data = SetActiveVersion.model_validate(request.json)
    logger.info(
        "Setting active version: model_id=%s version_id=%s",
        model_id,
        data.versionId,
    )

    service = get_service()
    model = service.set_active_version(model_id, data.versionId)

    model_with_versions = service.get_model(model_id)
    logger.info("Set active version: model_id=%s active_version=%s", model_id, model.activeVersionId)
    return jsonify(model_to_response(service, model_with_versions))
