from __future__ import annotations

import base64
from dataclasses import dataclass
from io import BytesIO
from pathlib import Path
from typing import Any

from backend.constants import DOCX_MIME_TYPE, PPTX_MIME_TYPE
from backend.services.generated_document.generated_document_metadata import LINEAGE_ROLE_HISTORY, get_document_metadata
from backend.services.pptx_artifact_service import safe_filename


class PreviewArtifactNotFound(Exception):
    """Raised when the requested artifact cannot be previewed."""


class PreviewArtifactGone(Exception):
    """Raised when a valid generated document reference points to purged History content."""


class PreviewConversionError(Exception):
    """Raised when the requested artifact exists but cannot be converted."""


@dataclass(frozen=True)
class GeneratedDocumentPreview:
    pdf_bytes: bytes
    filename: str


def build_generated_document_preview_pdf(
    artifacts_meta: dict[str, Any] | None,
    art_id: str,
    art_index: int,
) -> GeneratedDocumentPreview:
    document_part, artifact = _get_previewable_document_part(artifacts_meta, art_id, art_index)
    data_base64 = document_part.get("dataBase64")
    if not isinstance(data_base64, str) or not data_base64:
        raise PreviewArtifactNotFound("Generated document artifact does not contain inline data")

    try:
        document_bytes = base64.b64decode(data_base64, validate=True)
    except ValueError as err:
        raise PreviewConversionError("Generated document artifact contains invalid inline data") from err

    input_filename = _document_source_filename(document_part, artifact)
    if _is_docx_part(document_part):
        pdf_bytes = convert_docx_to_pdf(document_bytes, input_filename)
    else:
        pdf_bytes = convert_pptx_to_pdf(document_bytes, input_filename)
    return GeneratedDocumentPreview(pdf_bytes=pdf_bytes, filename=_preview_filename(input_filename))


def convert_pptx_to_pdf(pptx_bytes: bytes, input_filename: str) -> bytes:
    safe_input_filename = safe_filename(input_filename or "presentation.pptx")
    if not safe_input_filename.lower().endswith(".pptx"):
        safe_input_filename = f"{safe_input_filename}.pptx"
    return _convert_office_document_to_pdf(pptx_bytes, safe_input_filename, format_label="PPTX")


def convert_docx_to_pdf(docx_bytes: bytes, input_filename: str) -> bytes:
    safe_input_filename = safe_filename(input_filename or "document.docx")
    if not safe_input_filename.lower().endswith(".docx"):
        safe_input_filename = f"{safe_input_filename}.docx"
    return _convert_office_document_to_pdf(docx_bytes, safe_input_filename, format_label="DOCX")


def _convert_office_document_to_pdf(document_bytes: bytes, input_filename: str, *, format_label: str) -> bytes:
    document_stream = BytesIO(document_bytes)
    document_stream.name = input_filename

    try:
        import dataiku
        from dataikuapi.dss.document_extractor import DocumentExtractor, LocalFileDocumentRef

        client = dataiku.api_client()
        project = client.get_default_project()
        doc_extractor = DocumentExtractor(client, project.project_key)
        response = doc_extractor.convert_to_pdf(document=LocalFileDocumentRef(document_stream))
    except Exception as err:
        raise PreviewConversionError(f"{format_label} to PDF conversion failed") from err

    if not response.success:
        raise PreviewConversionError(f"{format_label} to PDF conversion failed")

    try:
        with response.stream() as stream:
            pdf_bytes = bytes(stream.content)
    except Exception as err:
        raise PreviewConversionError(f"{format_label} to PDF conversion failed") from err

    if not pdf_bytes:
        raise PreviewConversionError(f"{format_label} to PDF conversion produced an empty PDF")
    return pdf_bytes


def _get_previewable_document_part(
    artifacts_meta: dict[str, Any] | None, art_id: str, art_index: int
) -> tuple[dict[str, Any], dict[str, Any]]:
    if not isinstance(artifacts_meta, dict) or art_id not in artifacts_meta:
        raise PreviewArtifactNotFound("Artifact id not found")
    if art_index < 0:
        raise PreviewArtifactNotFound("Artifact index out of range")

    artifact_group = artifacts_meta[art_id]
    if not isinstance(artifact_group, dict):
        raise PreviewArtifactNotFound("Artifact group is malformed")
    artifacts = artifact_group.get("artifacts")
    if not isinstance(artifacts, list):
        raise PreviewArtifactNotFound("Artifact group is malformed")
    if art_index >= len(artifacts):
        raise PreviewArtifactNotFound("Artifact index out of range")

    artifact = artifacts[art_index]
    if not isinstance(artifact, dict):
        raise PreviewArtifactNotFound("Artifact is malformed")
    document_metadata = get_document_metadata(artifact)
    if document_metadata and document_metadata.get("lineageRole") == LINEAGE_ROLE_HISTORY:
        raise PreviewArtifactGone("Generated document content is no longer retained")
    parts = artifact.get("parts")
    if not isinstance(parts, list):
        raise PreviewArtifactNotFound("Artifact parts are malformed")
    for part in parts:
        if not isinstance(part, dict):
            continue
        if part.get("type") == "DATA_INLINE" and (_is_pptx_part(part) or _is_docx_part(part)):
            return part, artifact

    raise PreviewArtifactNotFound("Artifact is not a previewable generated document")


def _get_pptx_part(
    artifacts_meta: dict[str, Any] | None, art_id: str, art_index: int
) -> tuple[dict[str, Any], dict[str, Any]]:
    """Backward-compatible PPTX-only lookup retained for callers outside this module."""

    part, artifact = _get_previewable_document_part(artifacts_meta, art_id, art_index)
    if not _is_pptx_part(part):
        raise PreviewArtifactNotFound("Artifact is not a previewable PPTX")
    return part, artifact


def _is_pptx_part(part: dict[str, Any]) -> bool:
    filename = part.get("filename")
    if isinstance(filename, str) and filename.strip().lower().endswith(".pptx"):
        return True
    mime_type = part.get("mimeType")
    return isinstance(mime_type, str) and mime_type == PPTX_MIME_TYPE


def _is_docx_part(part: dict[str, Any]) -> bool:
    filename = part.get("filename")
    if isinstance(filename, str) and filename.strip().lower().endswith(".docx"):
        return True
    mime_type = part.get("mimeType")
    return isinstance(mime_type, str) and mime_type == DOCX_MIME_TYPE


def _document_source_filename(part: dict[str, Any], artifact: dict[str, Any]) -> str:
    filename = part.get("filename")
    if isinstance(filename, str) and filename.strip():
        return filename.strip()
    artifact_name = artifact.get("name")
    if isinstance(artifact_name, str) and artifact_name.strip():
        extension = "docx" if _is_docx_part(part) else "pptx"
        return f"{artifact_name.strip()}.{extension}"
    return "document.docx" if _is_docx_part(part) else "presentation.pptx"


def _pptx_source_filename(part: dict[str, Any], artifact: dict[str, Any]) -> str:
    """Backward-compatible alias for the original PPTX-specific helper."""

    return _document_source_filename(part, artifact)


def _preview_filename(input_filename: str) -> str:
    filename = safe_filename(input_filename or "presentation.pptx")
    return f"{Path(filename).stem or 'presentation'}.pdf"
