"""
File/attachment storage for "file" (Masters) and file_upload/image_upload/
attachment (Forms) field types. Local disk under backend/uploads/{tenant_id}/
— not a bare StaticFiles mount, since that would bypass auth entirely;
downloads go through get_auth_context + a tenant-scoped row lookup like
every other endpoint in this app.
"""

import asyncio
import re
import uuid
from pathlib import Path

from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import FileResponse

from app.core.database import get_db
from app.core.security import AuthContext, get_auth_context
from app.crud.base import CRUDBase
from app.models.upload import UploadedFile
from app.schemas.upload import UploadedFileRead
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(prefix="/uploads", tags=["uploads"])
crud = CRUDBase[UploadedFile, UploadedFileRead, UploadedFileRead](UploadedFile)

UPLOAD_ROOT = Path(__file__).resolve().parent.parent.parent / "uploads"
_UNSAFE_CHARS = re.compile(r"[^A-Za-z0-9._-]+")


def _safe_filename(filename: str) -> str:
    name = _UNSAFE_CHARS.sub("_", filename.strip())[-150:]
    return name or "file"


def _write_sync(path: Path, data: bytes) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_bytes(data)


def _delete_sync(path: Path) -> None:
    path.unlink(missing_ok=True)


@router.post("", response_model=UploadedFileRead, status_code=status.HTTP_201_CREATED)
async def upload_file(
    file: UploadFile = File(...),
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
) -> UploadedFile:
    data = await file.read()
    file_id = uuid.uuid4()
    disk_name = f"{file_id}_{_safe_filename(file.filename or 'file')}"
    storage_path = UPLOAD_ROOT / str(ctx.tenant_id) / disk_name
    await asyncio.to_thread(_write_sync, storage_path, data)

    record = UploadedFile(
        id=file_id,
        tenant_id=ctx.tenant_id,
        filename=file.filename or "file",
        content_type=file.content_type or "application/octet-stream",
        size_bytes=len(data),
        storage_path=str(storage_path),
        created_by=ctx.user_id,
    )
    db.add(record)
    await db.flush()
    await db.refresh(record)
    return record


@router.get("/{file_id}", response_model=UploadedFileRead)
async def get_file_metadata(
    file_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
) -> UploadedFile:
    return await crud.get(db, ctx.tenant_id, file_id)


@router.get("/{file_id}/download")
async def download_file(
    file_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
) -> FileResponse:
    record = await crud.get(db, ctx.tenant_id, file_id)
    path = Path(record.storage_path)
    if not path.is_file():
        raise HTTPException(status_code=404, detail="File is missing from storage")
    return FileResponse(path, media_type=record.content_type, filename=record.filename)


@router.delete("/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_file(
    file_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
) -> None:
    # A file can be attached from either a Master or a Form field, so there's
    # no single module to gate this on the way other routers do — any
    # authenticated tenant member may delete an upload, same as upload_file.
    record = await crud.get(db, ctx.tenant_id, file_id)
    path = Path(record.storage_path)
    await crud.delete(db, ctx.tenant_id, file_id)
    await asyncio.to_thread(_delete_sync, path)
