from __future__ import annotations

import mimetypes
import shutil
from pathlib import Path
from typing import Any
from urllib.parse import quote

from app.services.document_preview import DocumentPreviewAssets
from app.services.expense_claim_attachment_storage import ExpenseClaimAttachmentStorage


class ExpenseClaimAttachmentPresentation:
    def __init__(self, storage: ExpenseClaimAttachmentStorage) -> None:
        self.storage = storage

    def build_preview_meta(
        self,
        *,
        file_path: Path,
        media_type: str,
        ocr_document: Any | None,
    ) -> dict[str, Any]:
        filename = file_path.name
        storage_key = self.storage.to_storage_key(file_path)
        preview_kind = self.resolve_preview_kind(media_type, filename)

        preview_data_url = str(getattr(ocr_document, "preview_data_url", "") or "").strip()
        preview_source_kind = str(getattr(ocr_document, "preview_kind", "") or "").strip()
        if preview_source_kind == "image" and preview_data_url:
            preview_asset = self._write_preview_asset_from_data_url(
                attachment_dir=file_path.parent,
                original_filename=filename,
                preview_data_url=preview_data_url,
            )
            if preview_asset is not None:
                preview_path, preview_media_type, preview_file_name = preview_asset
                return {
                    "previewable": True,
                    "preview_kind": "image",
                    "preview_storage_key": self.storage.to_storage_key(preview_path),
                    "preview_media_type": preview_media_type,
                    "preview_file_name": preview_file_name,
                    "preview_rendered_with": DocumentPreviewAssets.renderer_id_for_source(media_type),
                }

        preview_source_path = getattr(ocr_document, "preview_source_path", None)
        if preview_source_kind == "image" and preview_source_path:
            preview_asset = self._copy_preview_asset_from_source(
                attachment_dir=file_path.parent,
                original_filename=filename,
                preview_source_path=Path(preview_source_path),
                preview_media_type=str(getattr(ocr_document, "preview_media_type", "") or ""),
            )
            if preview_asset is not None:
                preview_path, preview_media_type, preview_file_name = preview_asset
                return {
                    "previewable": True,
                    "preview_kind": "image",
                    "preview_storage_key": self.storage.to_storage_key(preview_path),
                    "preview_media_type": preview_media_type,
                    "preview_file_name": preview_file_name,
                    "preview_rendered_with": DocumentPreviewAssets.renderer_id_for_source(media_type),
                }

        if preview_kind:
            return {
                "previewable": True,
                "preview_kind": preview_kind,
                "preview_storage_key": storage_key,
                "preview_media_type": media_type,
                "preview_file_name": filename,
                "preview_rendered_with": "",
            }

        return {
            "previewable": False,
            "preview_kind": "",
            "preview_storage_key": "",
            "preview_media_type": "",
            "preview_file_name": "",
            "preview_rendered_with": "",
        }

    @staticmethod
    def resolve_preview_kind(media_type: str | None, filename: str) -> str:
        resolved = str(media_type or "").strip() or (mimetypes.guess_type(filename)[0] or "")
        if resolved.startswith("image/"):
            return "image"
        if resolved == "application/pdf":
            return "pdf"
        return ""

    @staticmethod
    def decode_data_url(payload: str) -> tuple[str, bytes] | None:
        return DocumentPreviewAssets.decode_data_url(payload)

    def _write_preview_asset_from_data_url(
        self,
        *,
        attachment_dir: Path,
        original_filename: str,
        preview_data_url: str,
    ) -> tuple[Path, str, str] | None:
        return DocumentPreviewAssets.write_data_url_preview(
            preview_dir=attachment_dir,
            preview_name_stem=f"{Path(original_filename).stem}.preview",
            preview_data_url=preview_data_url,
        )

    def _copy_preview_asset_from_source(
        self,
        *,
        attachment_dir: Path,
        original_filename: str,
        preview_source_path: Path,
        preview_media_type: str,
    ) -> tuple[Path, str, str] | None:
        if not preview_source_path.exists() or not preview_source_path.is_file():
            return None

        suffix = preview_source_path.suffix or DocumentPreviewAssets.PDF_PREVIEW_SUFFIX
        preview_name = f"{Path(original_filename).stem}.preview{suffix}"
        preview_path = attachment_dir / preview_name
        shutil.copyfile(preview_source_path, preview_path)
        resolved_media_type = (
            preview_media_type
            or mimetypes.guess_type(preview_source_path.name)[0]
            or DocumentPreviewAssets.PDF_PREVIEW_MEDIA_TYPE
        )
        return preview_path, resolved_media_type, preview_name

    @staticmethod
    def build_preview_client_path(claim_id: str, item_id: str) -> str:
        return (
            "/reimbursements/claims/"
            f"{quote(str(claim_id or '').strip(), safe='')}"
            f"/items/{quote(str(item_id or '').strip(), safe='')}/attachment/preview"
        )

    @staticmethod
    def resolve_media_type(filename: str, *, fallback: str | None = None) -> str:
        guessed = mimetypes.guess_type(filename)[0]
        return str(guessed or fallback or "application/octet-stream")

    @staticmethod
    def is_previewable_media_type(media_type: str | None, filename: str) -> bool:
        resolved = str(media_type or "").strip() or (mimetypes.guess_type(filename)[0] or "")
        return resolved.startswith("image/") or resolved == "application/pdf"

    @staticmethod
    def resolve_display_name(storage_key: str | None) -> str:
        return Path(str(storage_key or "").strip()).name

    @classmethod
    def merge_reference(cls, current_invoice_id: str | None, next_invoice_id: str | None) -> str | None:
        normalized_next = str(next_invoice_id or "").strip()
        if not normalized_next:
            return None

        normalized_current = str(current_invoice_id or "").strip()
        if (
            normalized_current
            and cls.resolve_display_name(normalized_current) == cls.resolve_display_name(normalized_next)
        ):
            return normalized_current

        return normalized_next
