from collections.abc import Mapping
from typing import Any

from backend.services.generated_document.generated_document_metadata import DOCUMENT_METADATA_KEY
from backend.utils.logging_utils import get_logger

ARTIFACT_SUMMARIES_KEY = "__artifact_summaries__"
_DATA_INLINE_PART_TYPE = "DATA_INLINE"
_MAX_FIELD_VALUE_LENGTH = 500
logger = get_logger(__name__)


def build_artifact_summary_parts_for_message(message: Mapping[str, Any]) -> list[str]:
    """Build Artifact Summary text parts from a persisted assistant message."""
    if message.get("role") != "assistant":
        return []

    message_id = _clean_text(message.get("id"))
    if not message_id:
        return []

    parts: list[str] = []
    artifacts_metadata = message.get("artifacts_metadata")
    if isinstance(artifacts_metadata, Mapping):
        for artifacts_key, metadata_value in artifacts_metadata.items():
            parts.extend(build_artifact_summary_parts_for_artifacts_group(message_id, artifacts_key, metadata_value))

    actions = message.get("actions")
    chart_plans = actions.get("chart_plans") if isinstance(actions, Mapping) else None
    if isinstance(chart_plans, list):
        for plan in chart_plans:
            chart_summary = _build_chart_plan_summary_part(plan, msg_id=message_id)
            if chart_summary:
                parts.append(chart_summary)
    return parts


def build_artifact_summary_parts_for_artifacts_group(
    message_id: str, artifacts_key: Any, metadata_value: Any
) -> list[str]:
    """Build Artifact Summary text parts from one artifacts_metadata entry."""
    if not isinstance(metadata_value, Mapping):
        return []

    artifacts_id = _clean_text(metadata_value.get("artifacts_id") or artifacts_key)
    if not artifacts_id:
        return []

    artifacts = metadata_value.get("artifacts")
    if not isinstance(artifacts, list):
        return []

    parts: list[str] = []
    for artifact_index, artifact_value in enumerate(artifacts):
        artifact_summary_part = _build_artifact_summary_part(message_id, artifacts_id, artifact_index, artifact_value)
        if artifact_summary_part:
            parts.append(artifact_summary_part)
    return parts


def _build_artifact_summary_part(
    message_id: str, artifacts_id: str, artifact_index: int, artifact_value: Any
) -> str | None:
    if not isinstance(artifact_value, Mapping) or artifact_value.get("type") == "REASONING":
        return None

    artifact_lines = [
        "- reference: " f"message_id={message_id}, " f"artifacts_id={artifacts_id}, " f"artifact_index={artifact_index}"
    ]
    _append_text_field(artifact_lines, "name", artifact_value.get("name"))
    _append_text_field(artifact_lines, "type", artifact_value.get("type"))
    _append_text_field(artifact_lines, "description", artifact_value.get("description"))
    _append_generated_document_metadata(artifact_lines, artifact_value)
    _append_first_data_inline_file_fields(artifact_lines, artifact_value)
    return "Artifact generated by this assistant message:\n" + "\n".join(artifact_lines)


def render_artifact_summary_messages(messages: list[Any] | None) -> list[Any]:
    rendered_messages: list[Any] = []
    for message in messages or []:
        if not isinstance(message, Mapping):
            rendered_messages.append(message)
            continue

        if ARTIFACT_SUMMARIES_KEY not in message:
            rendered_messages.append(message)
            continue

        artifact_summary_parts = message.get(ARTIFACT_SUMMARIES_KEY)
        rendered_message = {key: value for key, value in message.items() if key != ARTIFACT_SUMMARIES_KEY}
        if not isinstance(artifact_summary_parts, list) or not artifact_summary_parts:
            rendered_messages.append(rendered_message)
            continue

        artifact_summary_parts = [part for part in artifact_summary_parts if isinstance(part, str) and part]
        if not artifact_summary_parts:
            rendered_messages.append(rendered_message)
            continue

        rendered_messages.append(_append_text_parts_to_message(rendered_message, artifact_summary_parts))

    return rendered_messages


def _append_text_parts_to_message(message: Mapping[str, Any], texts: list[str]) -> dict[str, Any]:
    rendered_message = dict(message)
    parts = rendered_message.get("parts")
    if isinstance(parts, list):
        rendered_message["parts"] = [*parts, *[_build_text_part(text) for text in texts]]
        rendered_message.pop("content", None)
        return rendered_message

    content = rendered_message.pop("content", "")
    rendered_message["parts"] = [_build_text_part(content), *[_build_text_part(text) for text in texts]]
    return rendered_message


def _build_text_part(text: Any) -> dict[str, Any]:
    return {"type": "TEXT", "text": "" if text is None else str(text)}


def _append_text_field(lines: list[str], label: str, value: Any) -> None:
    normalized = _clean_text(value)
    if normalized:
        lines.append(f"  {label}: {normalized}")


def _append_generated_document_metadata(lines: list[str], artifact: Mapping[str, Any]) -> None:
    metadata = artifact.get(DOCUMENT_METADATA_KEY)
    if not isinstance(metadata, Mapping):
        return

    _append_text_field(lines, "lineageId", metadata.get("lineageId"))
    _append_text_field(lines, "lineageRole", metadata.get("lineageRole"))
    _append_text_field(lines, "revisionStatus", metadata.get("revisionStatus"))
    source = metadata.get("source")
    if isinstance(source, Mapping):
        source_message_id = _clean_text(source.get("messageId"))
        source_artifacts_id = _clean_text(source.get("artifactsId"))
        source_artifact_index = source.get("artifactIndex")
        if source_message_id and source_artifacts_id and isinstance(source_artifact_index, int):
            lines.append(
                "  derivationSource: "
                f"message_id={source_message_id}, artifacts_id={source_artifacts_id}, "
                f"artifact_index={source_artifact_index}"
            )

    lineage_role = metadata.get("lineageRole")
    if lineage_role == "proposed":
        lines.append("  revisionUse: preferred_for_implicit_follow_up_in_this_lineage")
    elif lineage_role == "current":
        lines.append("  revisionUse: use_when_this_lineage_has_no_proposed_artifact")
    elif lineage_role == "history":
        lines.append("  revisionUse: unavailable_content_no_longer_retained")


def _append_first_data_inline_file_fields(lines: list[str], artifact: Mapping[str, Any]) -> None:
    metadata = artifact.get(DOCUMENT_METADATA_KEY)
    if isinstance(metadata, Mapping):
        filename = _clean_string(metadata.get("filename"))
        mime_type = _clean_string(metadata.get("mimeType"))
        if filename:
            lines.append(f"  filename: {filename}")
        if mime_type:
            lines.append(f"  mimeType: {mime_type}")
        if filename or mime_type:
            return

    parts = artifact.get("parts")
    if not isinstance(parts, list):
        return

    for part in parts:
        if not isinstance(part, Mapping) or part.get("type") != _DATA_INLINE_PART_TYPE:
            continue

        filename = _clean_string(part.get("filename"))
        mime_type = _clean_string(part.get("mimeType"))
        if not filename and not mime_type:
            continue

        if filename:
            lines.append(f"  filename: {filename}")
        if mime_type:
            lines.append(f"  mimeType: {mime_type}")
        return


def _clean_string(value: Any) -> str:
    if not isinstance(value, str):
        return ""
    return _clean_text(value)


def _clean_text(value: Any) -> str:
    if value is None:
        return ""
    normalized = " ".join(str(value).split())
    if len(normalized) > _MAX_FIELD_VALUE_LENGTH:
        return normalized[: _MAX_FIELD_VALUE_LENGTH - 3].rstrip() + "..."
    return normalized


def _chart_status(plan: Mapping, valid_chart: bool, kind: str | None, reason: str | None, has_data: bool) -> str:
    if (
        plan.get("generation_failed") is True
        or not valid_chart
        or (kind == "none" and not reason)
        or (kind != "none" and not has_data)
    ):
        return "failed"
    if kind == "none":
        return "not_generated"
    return "generated"


def _build_chart_plan_summary_part(plan: Any, msg_id: str) -> str | None:
    if not isinstance(plan, Mapping):
        return None

    header = f"- reference: message_id={msg_id}"
    plan_idx = plan.get("chart_plan_index")
    if isinstance(plan_idx, int):
        header += f", chart_plan_index={plan_idx}"
    lines = [header]

    artifact_id = _clean_text(plan.get("artifacts_id"))
    artifact_idx = plan.get("artifact_index")
    if artifact_id and isinstance(artifact_idx, int):
        lines.append(f"  sourceArtifact: artifacts_id={artifact_id}, artifact_index={artifact_idx}")

    chart = plan.get("chart_plan")
    valid_chart = isinstance(chart, Mapping)
    chart = chart if valid_chart else {}
    data = chart.get("chart_data")
    kind = _clean_text(chart.get("chart_type"))
    reason = _clean_text(chart.get("justification"))
    has_data = isinstance(data, Mapping)

    status = _chart_status(plan, valid_chart, kind, reason, has_data)
    lines.append(f"  status: {status}")

    if status == "not_generated":
        lines.append(f"  reason: {reason}")
    elif status == "generated":
        _append_text_field(lines, "title", data.get("title"))
        _append_text_field(lines, "chartType", kind or data.get("kind"))

    summary = "Chart generation result for this assistant response:\n" + "\n".join(lines)
    logger.debug("Final chart history summary for message_id=%s:\n%s", msg_id, summary)
    return summary
