from __future__ import annotations

import base64
import re
from typing import Any

from backend.services.generated_document.generated_document_metadata import (
    extract_pptx_deck_yaml as extract_pptx_deck_yaml,
)
from backend.services.generated_document.generated_document_metadata import (
    extract_pptx_deck_yaml_from_artifact as extract_pptx_deck_yaml_from_artifact,
)
from backend.services.generated_document.generated_document_metadata import (
    extract_pptx_image_assets as extract_pptx_image_assets,
)
from backend.services.generated_document.generated_document_metadata import (
    extract_pptx_image_assets_from_artifact as extract_pptx_image_assets_from_artifact,
)
from backend.utils.llm_utils import add_completion_msgs

MAX_PPTX_GENERATION_ATTEMPTS = 5
PPTX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
_PPTX_EXTENSION_RE = re.compile(r"\.pptx$", re.IGNORECASE)
_FILENAME_LABEL_PATTERN = r"(?:nom\s+du\s+fichier|filename)"
_RENAME_COMMAND_PATTERN = r"(?:nomme|appelle|name|call)"
_OPTIONAL_TARGET_PATTERN = r"(?:moi\s+)?(?:the\s+|le\s+)?"
_TARGET_OBJECT_PATTERN = r"(?:fichier|pptx|document|deck|presentation|présentation)"
_OPEN_QUOTE_PATTERN = r"""["'«“]"""
_CLOSE_QUOTE_PATTERN = r"""["'»”]"""
_QUOTED_FILENAME_FROM_PROMPT_PATTERNS = [
    re.compile(
        rf"""{_FILENAME_LABEL_PATTERN}\s*[:=]\s*{_OPEN_QUOTE_PATTERN}([^"'\n\r«»”“]+){_CLOSE_QUOTE_PATTERN}(?:\.pptx)?(?:[\s.,;:!?]|$)""",
        re.IGNORECASE,
    ),
    re.compile(
        rf"""{_RENAME_COMMAND_PATTERN}\s+{_OPTIONAL_TARGET_PATTERN}{_TARGET_OBJECT_PATTERN}\s+{_OPEN_QUOTE_PATTERN}([^"'\n\r«»”“]+){_CLOSE_QUOTE_PATTERN}(?:\.pptx)?(?:[\s.,;:!?]|$)""",
        re.IGNORECASE,
    ),
]
_UNQUOTED_FILENAME_FROM_PROMPT_PATTERNS = [
    re.compile(
        rf"""{_FILENAME_LABEL_PATTERN}\s*[:=]\s*([a-z0-9][a-z0-9_-]*)(?:\.pptx)?(?:[.,;:!?]|$)""",
        re.IGNORECASE,
    ),
    re.compile(
        rf"""{_RENAME_COMMAND_PATTERN}\s+{_OPTIONAL_TARGET_PATTERN}{_TARGET_OBJECT_PATTERN}\s+([a-z0-9][a-z0-9_-]*)(?:\.pptx)?(?:[.,;:!?]|$)""",
        re.IGNORECASE,
    ),
]


def execute_llm_completion(project, llm_id: str, messages: list[dict[str, Any]]):
    """Run one chat completion against the configured DSS LLM with ordered text or multipart messages."""

    completion = project.get_llm(llm_id).new_completion()
    add_completion_msgs(completion, messages)
    return completion.execute()


def extract_llm_text(response) -> str:
    """Extract non-empty text from an LLM response and normalize transport/runtime failures into ValueError."""

    if response is None:
        raise ValueError("PPTX generation returned no response")
    if not getattr(response, "success", False):
        raw = getattr(response, "_raw", {}) or {}
        raise ValueError(raw.get("errorMessage") or "PPTX generation failed")
    text = getattr(response, "text", "") or ""
    if not text.strip():
        raise ValueError("PPTX generation returned empty text")
    return text.strip()


def build_pptx_filename(
    *,
    user_prompt: str,
    requested_filename: str | None = None,
    deck_title: str | None = None,
) -> str:
    """Build a deterministic download filename from explicit naming first, then deck title, then user prompt."""

    explicit_filename = requested_filename or extract_requested_filename(user_prompt)
    if explicit_filename:
        return f"{_slugify_filename_source(explicit_filename)}.pptx"

    if isinstance(deck_title, str) and deck_title.strip():
        return f"{_slugify_filename_source(deck_title)}.pptx"

    return f"{_slugify_filename_source(user_prompt)}.pptx"


def extract_requested_filename(user_prompt: str) -> str | None:
    """Extract an explicit file naming instruction from the user prompt when one is clearly present."""

    normalized_prompt = (user_prompt or "").strip()
    if not normalized_prompt:
        return None

    for patterns in (_QUOTED_FILENAME_FROM_PROMPT_PATTERNS, _UNQUOTED_FILENAME_FROM_PROMPT_PATTERNS):
        for pattern in patterns:
            match = pattern.search(normalized_prompt)
            if match:
                candidate = _clean_filename_candidate(match.group(1))
                if candidate:
                    return candidate
    return None


def _clean_filename_candidate(candidate: str) -> str | None:
    cleaned = (candidate or "").strip()
    if not cleaned:
        return None
    cleaned = _PPTX_EXTENSION_RE.sub("", cleaned).strip()
    return cleaned or None


def _slugify_filename_source(value: str) -> str:
    normalized = re.sub(r"[^a-z0-9]+", "-", (value or "").lower()).strip("-")
    normalized = normalized[:80].strip("-")
    return normalized or "generated-presentation"


def build_inline_pptx_artifacts(
    *,
    title: str,
    filename: str,
    pptx_bytes: bytes,
    mime_type: str,
    deck_yaml: str,
    image_assets: dict[str, dict[str, Any]] | None = None,
    document_metadata: dict[str, Any] | None = None,
    markdown_snippet: str | None = None,
    text_snippet: str | None = None,
) -> list[dict]:
    """Wrap a rendered PPTX binary as one inline artifact payload consumable by the chat/event layer."""

    encoded = base64.b64encode(pptx_bytes).decode("ascii")
    inline_part = {
        "type": "DATA_INLINE",
        "mimeType": mime_type,
        "dataBase64": encoded,
        "filename": filename,
        "markdownSnippet": markdown_snippet or "",
        "textSnippet": text_snippet or "",
        "agentHubGenerationMetadata": {
            "deckYaml": deck_yaml,
            **({"imageAssets": image_assets} if image_assets else {}),
        },
    }
    artifact = {
        "name": title or "Presentation",
        "description": "Generated editable PowerPoint presentation",
        "parts": [inline_part],
    }
    if document_metadata:
        artifact["agentHubDocumentMetadata"] = document_metadata
    return [artifact]


def format_retryable_generation_error(error: Exception) -> str:
    """Convert any retryable generation failure into a short string that can be fed back into the repair prompt."""

    message = str(error).strip()
    return message or error.__class__.__name__
