from __future__ import annotations

from datetime import UTC, datetime
from pathlib import Path
from types import SimpleNamespace
from typing import Any

from app.api.deps import CurrentUserContext
from app.models.financial_record import ExpenseClaim, ExpenseClaimItem
from app.services.document_preview import DocumentPreviewAssets
from app.services.expense_claim_attachment_commercial import (
    stage_attachment_deletion,
    stage_attachment_replacement,
)
from app.services.ocr import OcrService
from app.services.ocr_commercial import content_digest, trusted_ocr_operation_context
from app.services.receipt_folder import ReceiptFolderService


class ExpenseClaimAttachmentOperationsMixin:
    def upload_claim_item_attachment(
        self,
        *,
        claim_id: str,
        item_id: str,
        filename: str,
        content: bytes,
        media_type: str | None,
        current_user: CurrentUserContext,
        source_receipt_id: str = "",
        request_id: str = "",
        commit: bool = True,
        link_source_receipt: bool = True,
        write_audit: bool = True,
        refresh_pre_review: bool = True,
    ) -> dict[str, Any] | None:
        claim, item = self._get_claim_item_or_raise(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
        if claim is None:
            return None

        self._ensure_draft_claim(claim)
        self._ensure_mutable_claim_item(item)
        normalized_name = self._attachment_storage.normalize_filename(filename)
        if not content:
            raise ValueError("上传文件不能为空。")

        stage_attachment_replacement(
            self.db,
            storage=self._attachment_storage,
            item=item,
            current_user=current_user,
            claim_id=claim.id,
            content=content,
            request_id=request_id,
            source_receipt_id=source_receipt_id,
        )
        before_json = self._serialize_claim(claim)
        attachment_dir = self._attachment_storage.build_item_dir(claim.id, item.id)
        attachment_dir.mkdir(parents=True, exist_ok=True)

        file_path = attachment_dir / normalized_name
        file_path.write_bytes(content)
        resolved_media_type = self._attachment_presentation.resolve_media_type(
            normalized_name,
            fallback=media_type,
        )

        attachment_analysis = self._build_fallback_attachment_analysis(
            media_type=media_type,
            item=item,
        )
        source_receipt_document = self._resolve_source_receipt_document(
            source_receipt_id=source_receipt_id,
            current_user=current_user,
            fallback_filename=normalized_name,
            fallback_media_type=resolved_media_type,
        )
        ocr_document = None
        document_info = None
        requirement_check = None
        ocr_status = "empty"
        ocr_error = ""
        upload_ocr_document = None
        try:
            operation_context = trusted_ocr_operation_context(
                current_user,
                operation_scope=f"expense-claim-attachment:{claim.id}:{item.id}",
                content_digests=[content_digest(content)],
                run_id=f"claim-{claim.id}",
            )
            ocr_result = OcrService(
                self.db,
                operation_context=operation_context,
            ).recognize_files(
                [(normalized_name, content, media_type or "application/octet-stream")]
            )
            documents = list(ocr_result.documents or [])
            if documents:
                upload_ocr_document = documents[0]
        except Exception as exc:  # pragma: no cover - fallback path depends on OCR runtime
            ocr_error = str(exc)

        ocr_document = self._choose_attachment_ocr_document(
            source_receipt_document=source_receipt_document,
            upload_ocr_document=upload_ocr_document,
        )
        if ocr_document is not None:
            ocr_status = "recognized"
            ocr_error = ""
            document_info = self._build_attachment_document_info(ocr_document)
            self._backfill_item_type_from_attachment(
                item=item,
                document_info=document_info,
            )
            self._backfill_item_amount_from_attachment(
                item=item,
                document=ocr_document,
                document_info=document_info,
            )
            self._backfill_item_date_from_attachment(
                item=item,
                document=ocr_document,
                document_info=document_info,
            )
            self._backfill_item_reason_from_attachment(
                item=item,
                document=ocr_document,
                document_info=document_info,
            )
            requirement_check = self._build_attachment_requirement_check(
                item=item,
                document_info=document_info,
            )
            attachment_analysis = self._build_attachment_analysis(
                document=ocr_document,
                item=item,
                claim=claim,
                document_info=document_info,
                requirement_check=requirement_check,
            )
        elif ocr_error:
            ocr_status = "failed"
            attachment_analysis = self._build_failed_ocr_attachment_analysis(
                media_type=media_type,
                error_message=ocr_error,
                item=item,
            )

        item.invoice_id = self._attachment_storage.to_storage_key(file_path)
        preview_meta = self._attachment_presentation.build_preview_meta(
            file_path=file_path,
            media_type=resolved_media_type,
            ocr_document=ocr_document,
        )
        meta = {
            "file_name": normalized_name,
            "storage_key": item.invoice_id,
            "media_type": resolved_media_type,
            "size_bytes": len(content),
            "uploaded_at": datetime.now(UTC).isoformat(),
            "previewable": bool(preview_meta["previewable"]),
            "preview_kind": str(preview_meta["preview_kind"]),
            "preview_storage_key": str(preview_meta["preview_storage_key"]),
            "preview_media_type": str(preview_meta["preview_media_type"]),
            "preview_file_name": str(preview_meta["preview_file_name"]),
            "preview_rendered_with": str(preview_meta.get("preview_rendered_with") or ""),
            "analysis": attachment_analysis,
            "document_info": document_info,
            "requirement_check": requirement_check,
            "ocr_status": ocr_status,
            "ocr_error": ocr_error,
            "ocr_text": str(getattr(ocr_document, "text", "") or ""),
            "ocr_summary": str(getattr(ocr_document, "summary", "") or ""),
            "ocr_avg_score": float(getattr(ocr_document, "avg_score", 0.0) or 0.0),
            "ocr_line_count": int(getattr(ocr_document, "line_count", 0) or 0),
            "ocr_classification_source": str(
                getattr(ocr_document, "classification_source", "") or ""
            ),
            "ocr_classification_confidence": float(
                getattr(ocr_document, "classification_confidence", 0.0) or 0.0
            ),
            "ocr_classification_evidence": [
                str(item)
                for item in getattr(ocr_document, "classification_evidence", []) or []
                if str(item).strip()
            ],
            "ocr_warnings": [str(item) for item in getattr(ocr_document, "warnings", []) or []],
            "source_receipt_id": str(source_receipt_id or "").strip(),
        }
        self._attachment_storage.write_meta(file_path, meta)
        if link_source_receipt:
            ReceiptFolderService().save_linked_attachment(
                file_path=file_path,
                media_type=resolved_media_type,
                document=ocr_document,
                current_user=current_user,
                claim_id=claim.id,
                claim_no=claim.claim_no,
                item_id=item.id,
                source_receipt_id=source_receipt_id,
            )

        self._sync_claim_from_items(claim)
        if refresh_pre_review:
            self._refresh_claim_pre_review_flags(claim, is_application_claim=False)
        if commit:
            self.db.commit()
            self.db.refresh(claim)
        else:
            self.db.flush()

        if write_audit:
            self.audit_service.log_action(
                actor=current_user.name or current_user.username,
                action="expense_claim.attachment_upload",
                resource_type="expense_claim",
                resource_id=claim.id,
                before_json=before_json,
                after_json=self._serialize_claim(claim),
            )

        return {
            "message": f"{normalized_name} 已上传并关联到当前费用明细。",
            "claim_id": claim.id,
            "item_id": item.id,
            "invoice_id": item.invoice_id,
            "item_date": item.item_date.isoformat() if item.item_date else None,
            "item_type": item.item_type,
            "item_reason": item.item_reason,
            "item_location": item.item_location,
            "item_note": item.item_note,
            "item_amount": item.item_amount,
            "claim_amount": claim.amount,
            "claim_risk_flags": list(claim.risk_flags_json or []),
            "attachment": self._build_attachment_payload(item),
        }

    def _resolve_source_receipt_document(
        self,
        *,
        source_receipt_id: str,
        current_user: CurrentUserContext,
        fallback_filename: str,
        fallback_media_type: str,
    ) -> SimpleNamespace | None:
        normalized_receipt_id = str(source_receipt_id or "").strip()
        if not normalized_receipt_id:
            return None

        receipt_service = ReceiptFolderService()
        try:
            receipt = receipt_service.get_receipt(normalized_receipt_id, current_user)
        except FileNotFoundError:
            return None

        raw_meta = receipt.raw_meta if isinstance(receipt.raw_meta, dict) else {}
        fields = self._normalize_receipt_document_fields(
            [field.model_dump() for field in list(receipt.fields or [])]
        )
        if not fields:
            fields = self._normalize_receipt_document_fields(raw_meta.get("document_fields"))

        preview_source_path = None
        preview_media_type = ""
        preview_file_name = ""
        if str(raw_meta.get("preview_kind") or "").strip() == "image":
            try:
                preview_source_path, preview_media_type, preview_file_name = (
                    receipt_service.resolve_preview(
                        normalized_receipt_id,
                        current_user,
                    )
                )
            except FileNotFoundError:
                preview_source_path = None
                preview_media_type = ""
                preview_file_name = ""

        document = SimpleNamespace(
            filename=str(receipt.file_name or fallback_filename or "").strip(),
            media_type=str(
                receipt.media_type or fallback_media_type or "application/octet-stream"
            ).strip(),
            engine=str(receipt.engine or raw_meta.get("engine") or ""),
            model=str(receipt.model or raw_meta.get("model") or ""),
            text=str(receipt.ocr_text or raw_meta.get("ocr_text") or ""),
            summary=str(receipt.summary or raw_meta.get("summary") or ""),
            avg_score=float(receipt.avg_score or raw_meta.get("ocr_avg_score") or 0.0),
            line_count=int(receipt.line_count or raw_meta.get("ocr_line_count") or 0),
            page_count=max(1, int(receipt.page_count or raw_meta.get("page_count") or 1)),
            document_type=str(
                receipt.document_type or raw_meta.get("document_type") or "other"
            ).strip(),
            document_type_label=str(
                receipt.document_type_label or raw_meta.get("document_type_label") or "其他单据"
            ).strip(),
            scene_code=str(receipt.scene_code or raw_meta.get("scene_code") or "other").strip(),
            scene_label=str(
                receipt.scene_label or raw_meta.get("scene_label") or "其他票据"
            ).strip(),
            classification_source=str(
                raw_meta.get("ocr_classification_source") or "receipt_folder"
            ),
            classification_confidence=float(
                receipt.classification_confidence
                or raw_meta.get("ocr_classification_confidence")
                or 0.0
            ),
            classification_evidence=[
                str(value)
                for value in list(
                    receipt.classification_evidence
                    or raw_meta.get("ocr_classification_evidence")
                    or []
                )
                if str(value).strip()
            ],
            document_fields=fields,
            preview_kind=str(raw_meta.get("preview_kind") or ""),
            preview_data_url="",
            preview_source_path=str(preview_source_path or ""),
            preview_media_type=preview_media_type,
            preview_file_name=preview_file_name,
            warnings=[
                str(value)
                for value in list(receipt.warnings or raw_meta.get("ocr_warnings") or [])
                if str(value).strip()
            ],
        )
        return document if self._attachment_ocr_signal_score(document) > 0 else None

    @staticmethod
    def _normalize_receipt_document_fields(raw_fields: Any) -> list[dict[str, str]]:
        fields: list[dict[str, str]] = []
        for field in list(raw_fields or []):
            if isinstance(field, dict):
                key = str(field.get("key") or "").strip()
                label = str(field.get("label") or "").strip()
                value = str(field.get("value") or "").strip()
            else:
                key = str(getattr(field, "key", "") or "").strip()
                label = str(getattr(field, "label", "") or "").strip()
                value = str(getattr(field, "value", "") or "").strip()
            if label and value:
                fields.append({"key": key, "label": label, "value": value})
        return fields

    @classmethod
    def _choose_attachment_ocr_document(
        cls,
        *,
        source_receipt_document: Any | None,
        upload_ocr_document: Any | None,
    ) -> Any | None:
        source_score = cls._attachment_ocr_signal_score(source_receipt_document)
        upload_score = cls._attachment_ocr_signal_score(upload_ocr_document)
        if source_score <= 0:
            # OCR 已返回文档但没有任何有效信号时，也要进入高风险校验，
            # 不能回落成“待识别”的中风险状态放过普通图片或空白附件。
            return upload_ocr_document
        if upload_score <= 0:
            return source_receipt_document

        source_type = cls._attachment_document_type(source_receipt_document)
        upload_type = cls._attachment_document_type(upload_ocr_document)
        if source_type in {"", "other"} and upload_type not in {"", "other"}:
            return upload_ocr_document
        if source_type not in {"", "other"} and upload_type in {"", "other"}:
            return source_receipt_document
        if (
            cls._attachment_has_image_preview(source_receipt_document)
            and not cls._attachment_has_image_preview(upload_ocr_document)
            and source_score >= upload_score
        ):
            return source_receipt_document
        if source_type == upload_type and cls._attachment_document_field_count(
            source_receipt_document
        ) > cls._attachment_document_field_count(upload_ocr_document):
            return source_receipt_document
        if source_score > upload_score + 2:
            return source_receipt_document
        return upload_ocr_document

    @classmethod
    def _attachment_ocr_signal_score(cls, document: Any | None) -> int:
        if document is None:
            return 0
        score = 0
        document_type = cls._attachment_document_type(document)
        if document_type not in {"", "other"}:
            score += 4
        score += min(3, cls._attachment_document_field_count(document))
        if str(getattr(document, "text", "") or "").strip():
            score += 2
        if str(getattr(document, "summary", "") or "").strip():
            score += 1
        if int(getattr(document, "line_count", 0) or 0) > 0:
            score += 1
        return score

    @staticmethod
    def _attachment_document_type(document: Any | None) -> str:
        return str(getattr(document, "document_type", "") or "").strip().lower()

    @staticmethod
    def _attachment_document_field_count(document: Any | None) -> int:
        if document is None:
            return 0
        return len(list(getattr(document, "document_fields", []) or []))

    @staticmethod
    def _attachment_has_image_preview(document: Any | None) -> bool:
        if document is None:
            return False
        return str(getattr(document, "preview_kind", "") or "").strip() == "image" and bool(
            str(getattr(document, "preview_data_url", "") or "").strip()
            or str(getattr(document, "preview_source_path", "") or "").strip()
        )

    def get_claim_item_attachment_meta(
        self,
        *,
        claim_id: str,
        item_id: str,
        current_user: CurrentUserContext,
    ) -> dict[str, Any] | None:
        claim, item = self._get_claim_item_or_raise(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
        if claim is None:
            return None

        return self._build_attachment_payload(item, current_user=current_user)

    def get_claim_item_attachment_content(
        self,
        *,
        claim_id: str,
        item_id: str,
        current_user: CurrentUserContext,
    ) -> tuple[Path, str, str] | None:
        claim, item = self._get_claim_item_or_raise(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
        if claim is None:
            return None

        return self._resolve_item_attachment_content(item)

    def get_claim_item_attachment_preview_content(
        self,
        *,
        claim_id: str,
        item_id: str,
        current_user: CurrentUserContext,
    ) -> tuple[Path, str, str] | None:
        claim, item = self._get_claim_item_or_raise(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
        if claim is None:
            return None

        return self._resolve_item_attachment_preview_content(item, current_user=current_user)

    def delete_claim_item_attachment(
        self,
        *,
        claim_id: str,
        item_id: str,
        current_user: CurrentUserContext,
    ) -> dict[str, Any] | None:
        claim, item = self._get_claim_item_or_raise(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
        if claim is None:
            return None

        self._ensure_draft_claim(claim)
        self._ensure_mutable_claim_item(item)
        before_json = self._serialize_claim(claim)
        previous_invoice_id = str(item.invoice_id or "").strip()
        previous_name = self._attachment_presentation.resolve_display_name(item.invoice_id)
        stage_attachment_deletion(
            self.db,
            storage=self._attachment_storage,
            item=item,
        )
        item.invoice_id = None
        claim.risk_flags_json = self._remove_deleted_attachment_risk_flags(
            claim.risk_flags_json,
            item_id=item.id,
            invoice_id=previous_invoice_id,
        )

        self._sync_claim_from_items(claim)
        self._refresh_claim_pre_review_flags(claim, is_application_claim=False)
        self.db.commit()
        self.db.refresh(claim)

        self.audit_service.log_action(
            actor=current_user.name or current_user.username,
            action="expense_claim.attachment_delete",
            resource_type="expense_claim",
            resource_id=claim.id,
            before_json=before_json,
            after_json=self._serialize_claim(claim),
        )

        return {
            "message": f"{previous_name or '附件'} 已删除。",
            "claim_id": claim.id,
            "item_id": item.id,
            "invoice_id": item.invoice_id,
            "claim_risk_flags": list(claim.risk_flags_json or []),
            "attachment": None,
        }

    @staticmethod
    def _remove_deleted_attachment_risk_flags(
        risk_flags: Any,
        *,
        item_id: str | None,
        invoice_id: str | None,
    ) -> list[Any]:
        normalized_item_id = str(item_id or "").strip()
        normalized_invoice_id = str(invoice_id or "").strip()
        cleaned_flags: list[Any] = []
        for flag in list(risk_flags or []):
            if not isinstance(flag, dict):
                cleaned_flags.append(flag)
                continue

            source = str(flag.get("source") or "").strip()
            if source != "attachment_analysis":
                cleaned_flags.append(flag)
                continue

            flag_item_id = str(flag.get("item_id") or flag.get("itemId") or "").strip()
            flag_invoice_id = str(flag.get("invoice_id") or flag.get("invoiceId") or "").strip()
            matches_deleted_item = bool(normalized_item_id and flag_item_id == normalized_item_id)
            matches_deleted_invoice = bool(
                normalized_invoice_id and flag_invoice_id == normalized_invoice_id
            )
            if matches_deleted_item or matches_deleted_invoice:
                continue

            cleaned_flags.append(flag)
        return cleaned_flags

    def _get_claim_item_or_raise(
        self,
        *,
        claim_id: str,
        item_id: str,
        current_user: CurrentUserContext,
    ) -> tuple[ExpenseClaim | None, ExpenseClaimItem]:
        claim = self.get_claim(claim_id, current_user)
        if claim is None:
            return None, None  # type: ignore[return-value]

        item = next((entry for entry in claim.items if entry.id == item_id), None)
        if item is None:
            raise LookupError("Item not found")
        return claim, item

    def _resolve_item_attachment_content(self, item: ExpenseClaimItem) -> tuple[Path, str, str]:
        file_path = self._attachment_storage.resolve_item_path(item)
        if file_path is None or not file_path.exists():
            raise FileNotFoundError("Attachment not found")

        metadata = self._attachment_storage.read_meta(file_path)
        filename = str(metadata.get("file_name") or file_path.name)
        media_type = self._attachment_presentation.resolve_media_type(
            filename,
            fallback=str(metadata.get("media_type") or ""),
        )
        return file_path, media_type, filename

    def _repair_pdf_text_layer_metadata_if_needed(
        self,
        *,
        file_path: Path,
        metadata: dict[str, Any],
        item: ExpenseClaimItem | None = None,
    ) -> dict[str, Any]:
        if not metadata:
            return metadata

        media_type = str(
            metadata.get("media_type")
            or self._attachment_presentation.resolve_media_type(file_path.name)
        ).strip()
        if media_type != "application/pdf":
            return metadata

        ocr_text = str(metadata.get("ocr_text") or "")
        ocr_summary = str(metadata.get("ocr_summary") or "")
        if OcrService._placeholder_ratio(f"{ocr_summary}\n{ocr_text}") < 0.12:
            return metadata

        text_layer = OcrService(self.db)._extract_pdf_text_layer(file_path)
        repaired_text, used_text_layer = OcrService._choose_document_text(
            ocr_text=ocr_text,
            text_layer=text_layer,
        )
        if not used_text_layer or not repaired_text:
            return metadata

        repaired_summary = OcrService._summarize_text(repaired_text)
        document = SimpleNamespace(
            filename=str(metadata.get("file_name") or file_path.name),
            text=repaired_text,
            summary=repaired_summary,
            avg_score=float(metadata.get("ocr_avg_score") or 0.0),
            line_count=int(metadata.get("ocr_line_count") or 0),
            document_type="",
            document_type_label="",
            scene_code="",
            scene_label="",
            document_fields=[],
            warnings=[
                str(value)
                for value in list(metadata.get("ocr_warnings") or [])
                if str(value).strip()
            ],
        )
        document_info = self._build_attachment_document_info(document)
        document.document_type = document_info.get("document_type", "")
        document.document_type_label = document_info.get("document_type_label", "")
        document.scene_code = document_info.get("scene_code", "")
        document.scene_label = document_info.get("scene_label", "")
        document.document_fields = list(document_info.get("fields") or [])

        metadata["ocr_text"] = repaired_text
        metadata["ocr_summary"] = repaired_summary
        metadata["document_info"] = document_info
        metadata["previewable"] = True
        metadata["preview_kind"] = "pdf"
        metadata["preview_storage_key"] = str(
            metadata.get("storage_key") or self._attachment_storage.to_storage_key(file_path)
        )
        metadata["preview_media_type"] = "application/pdf"
        metadata["preview_file_name"] = str(metadata.get("file_name") or file_path.name)

        if item is not None:
            requirement_check = self._build_attachment_requirement_check(
                item=item,
                document_info=document_info,
            )
            metadata["requirement_check"] = requirement_check
            metadata["analysis"] = self._build_attachment_analysis(
                document=document,
                item=item,
                claim=getattr(item, "claim", None),
                document_info=document_info,
                requirement_check=requirement_check,
            )

        self._attachment_storage.write_meta(file_path, metadata)
        return metadata

    def _refresh_pdf_attachment_preview_meta_if_needed(
        self,
        *,
        file_path: Path,
        metadata: dict[str, Any],
    ) -> dict[str, Any]:
        if not metadata:
            return metadata

        media_type = str(
            metadata.get("media_type")
            or self._attachment_presentation.resolve_media_type(file_path.name)
        ).strip()
        if media_type != "application/pdf":
            return metadata

        preview_storage_key = str(metadata.get("preview_storage_key") or "").strip()
        preview_path = (
            self._attachment_storage.resolve_path(preview_storage_key)
            if preview_storage_key
            else None
        )
        if (
            preview_path is not None
            and preview_path.exists()
            and str(metadata.get("preview_kind") or "").strip() == "image"
            and str(metadata.get("preview_media_type") or "").strip()
            == DocumentPreviewAssets.PDF_PREVIEW_MEDIA_TYPE
            and str(metadata.get("preview_rendered_with") or "").strip()
            == DocumentPreviewAssets.PDF_RENDERER_ID
        ):
            return metadata

        preview_name = str(metadata.get("preview_file_name") or "").strip()
        if not preview_name or not preview_name.lower().endswith(
            DocumentPreviewAssets.PDF_PREVIEW_SUFFIX
        ):
            preview_name = f"{file_path.stem}.preview{DocumentPreviewAssets.PDF_PREVIEW_SUFFIX}"
        preview_path = file_path.parent / preview_name

        try:
            DocumentPreviewAssets.render_pdf_first_page(
                pdf_path=file_path,
                preview_path=preview_path,
                timeout_seconds=OcrService(self.db).settings.ocr_timeout_seconds,
            )
        except Exception:
            metadata.update(
                {
                    "previewable": True,
                    "preview_kind": "pdf",
                    "preview_storage_key": self._attachment_storage.to_storage_key(file_path),
                    "preview_media_type": "application/pdf",
                    "preview_file_name": file_path.name,
                    "preview_rendered_with": "",
                }
            )
            self._attachment_storage.write_meta(file_path, metadata)
            return metadata

        metadata.update(
            {
                "previewable": True,
                "preview_kind": "image",
                "preview_storage_key": self._attachment_storage.to_storage_key(preview_path),
                "preview_media_type": DocumentPreviewAssets.PDF_PREVIEW_MEDIA_TYPE,
                "preview_file_name": preview_path.name,
                "preview_rendered_with": DocumentPreviewAssets.PDF_RENDERER_ID,
            }
        )
        self._attachment_storage.write_meta(file_path, metadata)
        return metadata

    def _resolve_item_attachment_preview_content(
        self,
        item: ExpenseClaimItem,
        *,
        current_user: CurrentUserContext | None = None,
    ) -> tuple[Path, str, str]:
        file_path, media_type, filename = self._resolve_item_attachment_content(item)
        metadata = self._attachment_storage.read_meta(file_path)
        metadata = self._repair_attachment_metadata_from_source_receipt_if_needed(
            file_path=file_path,
            metadata=metadata,
            item=item,
            current_user=current_user,
        )
        metadata = self._repair_pdf_text_layer_metadata_if_needed(
            file_path=file_path,
            metadata=metadata,
            item=item,
        )
        metadata = self._refresh_pdf_attachment_preview_meta_if_needed(
            file_path=file_path,
            metadata=metadata,
        )
        preview_storage_key = str(metadata.get("preview_storage_key") or "").strip()
        preview_file_name = str(metadata.get("preview_file_name") or "").strip()
        preview_media_type = str(metadata.get("preview_media_type") or "").strip()

        if preview_storage_key:
            preview_path = self._attachment_storage.resolve_path(preview_storage_key)
            if preview_path is not None and preview_path.exists():
                resolved_name = preview_file_name or preview_path.name
                resolved_media_type = self._attachment_presentation.resolve_media_type(
                    resolved_name,
                    fallback=preview_media_type,
                )
                return preview_path, resolved_media_type, resolved_name

        if self._attachment_presentation.is_previewable_media_type(media_type, filename):
            return file_path, media_type, filename

        raise FileNotFoundError("Attachment preview not found")
