from __future__ import annotations

from copy import deepcopy
from typing import Any

from backend.services.artifact_models import ArtifactReference, ReadArtifactResult, ResolvedArtifactRead
from backend.database.user_store import UserStore
from backend.services.generated_document.generated_document_metadata import LINEAGE_ROLE_HISTORY, get_document_metadata

from .lookup import get_artifact_from_metadata
from .readers import read_artifact_payload

CURRENT_ASSISTANT_MESSAGE_ID_CONTEXT_KEY = "current_assistant_message_id"
CURRENT_TURN_ARTIFACTS_BY_MESSAGE_ID_CONTEXT_KEY = "current_turn_artifacts_by_message_id"


def resolve_artifact_read_for_llm_payload(
    *, message_id: str, artifacts_id: str, artifact_index: int, context: dict[str, Any] | None, app
) -> ResolvedArtifactRead:
    """Resolve one referenced conversation artifact and normalize it into an LLM-readable backend payload.

    This is the canonical read path shared by the `read_artifact` tool and by backend preparation flows such as
    `generate_pptx(source_artifacts=[...])`. It re-enters a Flask app context because artifact persistence goes
    through Flask-scoped storage helpers.
    """
    reference = ArtifactReference(
        message_id=message_id,
        artifacts_id=artifacts_id,
        artifact_index=artifact_index,
    )
    with app.app_context():
        store = _build_user_store_from_context(context)
        if store is None:
            return ResolvedArtifactRead(
                reference=reference,
                read_result=_build_not_found_result(reference=reference, reason="missing_runtime_user_context"),
            )

        artifacts_meta = store.get_message_artifacts_meta(message_id)
        if not artifacts_meta:
            # Current-turn artifacts are only persisted at the end of the assistant turn, so read the runtime cache too.
            artifacts_meta = _get_current_turn_artifacts_meta(context, message_id)
        if not artifacts_meta:
            return ResolvedArtifactRead(
                reference=reference,
                read_result=_build_not_found_result(reference=reference, reason="message_artifacts_not_found"),
            )

        artifact = get_artifact_from_metadata(
            artifacts_meta=artifacts_meta,
            artifacts_id=artifacts_id,
            artifact_index=artifact_index,
        )
        if artifact is None:
            return ResolvedArtifactRead(
                reference=reference,
                read_result=_build_not_found_result(reference=reference, reason="artifact_reference_not_found"),
            )

        document_metadata = get_document_metadata(artifact)
        if document_metadata and document_metadata.get("lineageRole") == LINEAGE_ROLE_HISTORY:
            return ResolvedArtifactRead(
                reference=reference,
                read_result=ReadArtifactResult(
                    result=_format_gone(
                        artifact=artifact,
                        message_id=message_id,
                        artifacts_id=artifacts_id,
                        artifact_index=artifact_index,
                    ),
                    status="gone",
                    read_mode="unavailable",
                ),
            )

        original_artifact = deepcopy(artifact)
        read_result = read_artifact_payload(
            artifact=artifact,
            message_id=message_id,
            artifacts_id=artifacts_id,
            artifact_index=artifact_index,
            format_unsupported=_format_unsupported,
        )
        if artifact != original_artifact:
            store.update_message(message_id, {"artifacts": artifacts_meta})
        return ResolvedArtifactRead(reference=reference, read_result=read_result)


def _build_user_store_from_context(context: dict[str, Any] | None) -> UserStore | None:
    """Build the store bound to the runtime caller, or return None when context is unusable."""

    if not isinstance(context, dict):
        return None

    # The tool runtime context carries the caller login even though it does not carry Flask globals.
    user_login = context.get("dku_user_login")
    if not isinstance(user_login, str) or not user_login.strip():
        return None

    return UserStore(user_login.strip(), [])


def _get_current_turn_artifacts_meta(
    context: dict[str, Any] | None,
    message_id: str,
) -> dict[str, Any] | None:
    """Return same-turn artifacts metadata from runtime context when the assistant message is not persisted yet."""
    if not isinstance(context, dict):
        return None

    # This registry is populated by the agent loop for artifacts produced earlier in the same assistant turn.
    current_turn_artifacts = context.get(CURRENT_TURN_ARTIFACTS_BY_MESSAGE_ID_CONTEXT_KEY)
    if not isinstance(current_turn_artifacts, dict):
        return None

    artifacts_meta = current_turn_artifacts.get(message_id)
    return artifacts_meta if isinstance(artifacts_meta, dict) else None


def _format_failure(*, message_id: str, artifacts_id: str, artifact_index: int, reason: str) -> str:
    """Format a stable not-found result when the artifact reference cannot be resolved."""

    return "\n".join(
        [
            "Artifact read result:",
            "- status: not_found",
            f"- reference: message_id={message_id}, artifacts_id={artifacts_id}, artifact_index={artifact_index}",
            f"- reason: {reason}",
        ]
    )


def _build_not_found_result(*, reference: ArtifactReference, reason: str) -> ReadArtifactResult:
    return ReadArtifactResult(
        result=_format_failure(
            message_id=reference.message_id,
            artifacts_id=reference.artifacts_id,
            artifact_index=reference.artifact_index,
            reason=reason,
        ),
        status="not_found",
        read_mode="",
    )


def _format_unsupported(
    *,
    artifact: dict[str, Any],
    message_id: str,
    artifacts_id: str,
    artifact_index: int,
    reason: str,
) -> str:
    """Format a stable unsupported result when no reader can handle the artifact."""

    artifact_name = artifact.get("name") if isinstance(artifact.get("name"), str) else ""
    artifact_type = artifact.get("type") if isinstance(artifact.get("type"), str) else "unknown"
    return "\n".join(
        [
            "Artifact read result:",
            "- status: unsupported",
            f"- reference: message_id={message_id}, artifacts_id={artifacts_id}, artifact_index={artifact_index}",
            f"- name: {artifact_name}",
            f"- type: {artifact_type}",
            "- read_mode: unsupported",
            f"- reason: {reason}",
        ]
    )


def _format_gone(
    *,
    artifact: dict[str, Any],
    message_id: str,
    artifacts_id: str,
    artifact_index: int,
) -> str:
    artifact_name = artifact.get("name") if isinstance(artifact.get("name"), str) else ""
    return "\n".join(
        [
            "Artifact read result:",
            "- status: gone",
            f"- reference: message_id={message_id}, artifacts_id={artifacts_id}, artifact_index={artifact_index}",
            f"- name: {artifact_name}",
            "- read_mode: unavailable",
            "- reason: content_no_longer_retained",
        ]
    )
