from functools import lru_cache
from pathlib import Path
import hashlib
import mimetypes
import re
import uuid

from app.core.config import settings
from app.storage.base import StorageProvider
from app.storage.exceptions import StorageConfigurationError, StorageValidationError
from app.storage.local import LocalDiskStorageProvider
from app.storage.schemas import FileMetadata, StoredFileMetadata

ALLOWED_EXTENSIONS = {".pdf", ".jpg", ".jpeg", ".png", ".webp", ".txt", ".zip"}
ALLOWED_MIME_TYPES = {
    "application/pdf",
    "image/jpeg",
    "image/png",
    "image/webp",
    "text/plain",
    "application/zip",
    "application/x-zip-compressed",
}


class StorageService:
    def __init__(self, provider: StorageProvider, *, max_upload_mb: int) -> None:
        self.provider = provider
        self.max_upload_mb = max(1, max_upload_mb)

    def save_file(
        self,
        *,
        namespace: str,
        content: bytes,
        original_filename: str,
        mime_type: str | None = None,
    ) -> StoredFileMetadata:
        self.validate_file_size(len(content))
        safe_name = self.safe_filename(original_filename)
        resolved_mime_type = mime_type or mimetypes.guess_type(safe_name)[0] or "application/octet-stream"
        self.validate_file_type(safe_name, resolved_mime_type)
        stored_filename = f"{uuid.uuid4().hex}-{safe_name}"
        return self.provider.save_file(
            namespace=namespace,
            content=content,
            original_filename=original_filename,
            stored_filename=stored_filename,
            mime_type=resolved_mime_type,
        )

    def open_file(self, storage_key: str) -> Path:
        return self.provider.open_file(storage_key)

    def delete_file(self, storage_key: str) -> None:
        self.provider.delete_file(storage_key)

    def file_exists(self, storage_key: str) -> bool:
        return self.provider.file_exists(storage_key)

    def get_file_metadata(self, storage_key: str) -> FileMetadata:
        return self.provider.get_file_metadata(storage_key)

    @staticmethod
    def calculate_checksum(content: bytes) -> str:
        return hashlib.sha256(content).hexdigest()

    @staticmethod
    def safe_filename(file_name: str) -> str:
        name = Path(file_name or "").name.strip()
        if not name:
            raise StorageValidationError("Dateiname fehlt")
        stem = Path(name).stem.strip().lower()
        stem = stem.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss")
        stem = re.sub(r"[^a-z0-9]+", "-", stem).strip("-")
        suffix = Path(name).suffix.lower()
        safe_stem = stem or uuid.uuid4().hex[:10]
        return f"{safe_stem}{suffix}" if suffix else safe_stem

    def validate_file_type(self, file_name: str, mime_type: str) -> None:
        extension = Path(file_name).suffix.lower()
        if extension not in ALLOWED_EXTENSIONS:
            raise StorageValidationError("Dateityp ist nicht erlaubt")
        if mime_type not in ALLOWED_MIME_TYPES:
            raise StorageValidationError("MIME-Type ist nicht erlaubt")

    def validate_file_size(self, size: int) -> None:
        if size <= 0:
            raise StorageValidationError("Upload-Datei ist leer")
        if size > self.max_upload_mb * 1024 * 1024:
            raise StorageValidationError("Upload-Datei ist zu groß")


@lru_cache
def get_storage_service() -> StorageService:
    if settings.storage_provider != "local":
        raise StorageConfigurationError("Nur STORAGE_PROVIDER=local ist aktuell implementiert")

    provider = LocalDiskStorageProvider(
        base_path=settings.storage_base_path,
        legacy_base_paths=[settings.knowledge_storage_path],
    )
    return StorageService(provider, max_upload_mb=settings.storage_max_upload_mb)
