"""Featured-image domain — per-entity photo upload (v1 parity port).

v1 source: /srv/apps/garden/app/main.py — ``FEATURED_IMAGE_ENTITIES`` (≈4740)
+ ``_set_featured_image`` (≈4752-4785) + the eight per-entity
``POST /{plural}/{id}/featured-image`` routes. v2 collapses those eight
routes into ONE (``POST /api/featured-image/{entity_type}/{entity_id}``,
routers/featured_images.py) over the same eight entity types.

Behavior kept from v1:

- The upload is saved through the notes-media pipeline (same dir layout,
  same ``YYYY-MM/<uuid><ext>`` relative paths the ``featured_image_path``
  columns already hold — the photo-path wart, kept on purpose).
- The PREVIOUS file is deleted from disk after the row points at the new one.
- One audit row per change (field ``featured_image_path``, old → new).

v2 deviations (deliberate, documented):

- Garden scoping (D1): the target row must belong to the caller's garden
  (varieties scope through their species join, like the archive/edit
  domains — the scoped fetch is reused from services/archive.py so there is
  exactly one implementation of that rule).
- NEW: ``remove`` support — v1 had no way to clear a featured image; the v2
  API accepts form field ``remove=1`` to clear the path and delete the file.
- JSON response instead of v1's 303 redirect (SPA client).
"""
from __future__ import annotations

from fastapi import UploadFile
from sqlalchemy.orm import Session

from ..errors import AppError
from ..models.catalog import Species, Variety
from ..models.garden import Area, Supply, WateringStation, Year
from ..models.planting import Plant, Planting
from . import media
from .archive import _get_scoped, entity_label
from .audit import log_edit

# entity_type -> model. Same eight types as v1's FEATURED_IMAGE_ENTITIES
# (which mapped to table names; v2 maps to models). Every model here has a
# ``featured_image_path`` column.
FEATURED_IMAGE_ENTITIES = {
    "area": Area,
    "planting": Planting,
    "plant": Plant,
    "species": Species,
    "variety": Variety,
    "station": WateringStation,
    "supply": Supply,
    "year": Year,
}

# --- error codes (this domain) ---
FEATURED_IMAGE_ENTITY_UNKNOWN = "featured_image_entity_unknown"
FEATURED_IMAGE_NO_FILE = "featured_image_no_file"


def _get_row(db: Session, garden_id: int, entity_type: str, entity_id: int):
    if entity_type not in FEATURED_IMAGE_ENTITIES:
        raise AppError(
            FEATURED_IMAGE_ENTITY_UNKNOWN,
            f"unknown entity type {entity_type!r}; must be one of "
            f"{sorted(FEATURED_IMAGE_ENTITIES)}",
            404,
        )
    # Reuse the archive domain's garden-scoped fetch (same entity key set;
    # handles the variety-via-species join). Raises AppError(NOT_FOUND, 404).
    return _get_scoped(db, entity_type, entity_id, garden_id)


def _out(entity_type: str, entity_id: int, path: str) -> dict:
    return {
        "ok": True,
        "entity_type": entity_type,
        "entity_id": entity_id,
        "featured_image_path": path,
    }


def set_featured_image(
    db: Session,
    garden_id: int,
    entity_type: str,
    entity_id: int,
    upload: UploadFile | None,
) -> dict:
    """Save the upload, point the entity row at it, delete the old file.

    Port of v1 ``_set_featured_image`` (image saved first, row updated, old
    file removed AFTER the DB change so a failed write never orphans the
    row's path).
    """
    row = _get_row(db, garden_id, entity_type, entity_id)
    if upload is None or not (upload.filename or "").strip():
        raise AppError(FEATURED_IMAGE_NO_FILE, "no file provided", 400)
    image_rel = media.save_upload(upload, "photo")
    old = (row.featured_image_path or "").strip()
    row.featured_image_path = image_rel
    log_edit(
        db, garden_id, entity_type, entity_id,
        "featured_image_path", old, image_rel,
        label=entity_label(db, entity_type, entity_id),
    )
    db.commit()
    if old:
        media.delete_note_file(old)
    return _out(entity_type, entity_id, image_rel)


def remove_featured_image(
    db: Session, garden_id: int, entity_type: str, entity_id: int
) -> dict:
    """Clear the featured image (v2 addition — v1 had no removal path).

    Idempotent: clearing an entity with no featured image is a silent no-op
    (no audit row, nothing deleted).
    """
    row = _get_row(db, garden_id, entity_type, entity_id)
    old = (row.featured_image_path or "").strip()
    if not old:
        return _out(entity_type, entity_id, "")
    row.featured_image_path = ""
    log_edit(
        db, garden_id, entity_type, entity_id,
        "featured_image_path", old, "",
        label=entity_label(db, entity_type, entity_id),
    )
    db.commit()
    media.delete_note_file(old)
    return _out(entity_type, entity_id, "")
