"""Wardrobe seeding: populate the unknown user's wardrobe from preset images.

Shared orchestration used by both the CLI script (scripts/seed_wardrobe.py)
and the admin HTTP endpoint (POST /api/v1/admin/seed).

Modes:
- "reset": wipe DB + storage (preserves GLOBAL_USER_ID), then seed
- "add":   keep existing data, append new garments to the unknown user

Ghost mannequin is always generated so VTON works immediately after seeding.
"""

from __future__ import annotations

import logging
import shutil
from collections.abc import Callable
from pathlib import Path
from typing import Literal

from pydantic import BaseModel
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import GLOBAL_USER_ID, settings
from app.models.face import FaceEmbedding
from app.models.garment import Garment
from app.models.user import User
from app.services.garment_service import GarmentService

logger = logging.getLogger(__name__)


DEFAULT_RESOURCE_DIRS: list[Path] = [
    Path("/home/ubuntu/ootd-poc/resources/men_garments"),
    Path("/home/ubuntu/ootd-poc/resources/women_garments"),
]

IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"}


class ResetSummary(BaseModel):
    deleted_garments: int
    deleted_faces: int
    deleted_users: int
    deleted_files: int


class SeedError(BaseModel):
    file: str
    stage: Literal["read", "analyze", "ghost"]
    message: str


class SeedSummary(BaseModel):
    mode: Literal["reset", "add"]
    images_processed: int = 0
    garments_created: int = 0
    ghosts_generated: int = 0
    errors: list[SeedError] = []
    reset_summary: ResetSummary | None = None


async def reset_all_data(db: AsyncSession) -> ResetSummary:
    """Delete all garments/faces/non-global users and wipe storage dirs.

    Preserves the unknown user (GLOBAL_USER_ID). Shared by /admin/reset and
    the seed service's reset mode.
    """
    garments = (await db.execute(select(Garment))).scalars().all()
    faces = (await db.execute(select(FaceEmbedding))).scalars().all()
    n_garments = len(garments)
    n_faces = len(faces)

    await db.execute(delete(Garment))
    await db.execute(delete(FaceEmbedding))

    result = await db.execute(select(User).where(User.id != GLOBAL_USER_ID))
    non_global_users = list(result.scalars().all())
    n_users = len(non_global_users)
    for u in non_global_users:
        await db.delete(u)

    await db.commit()

    deleted_files = 0
    base = Path(settings.UPLOAD_DIR)
    for sub in ("originals", "ghost_mannequin", "faces", "segmentation"):
        sub_dir = base / sub
        if not sub_dir.is_dir():
            continue
        for item in sub_dir.iterdir():
            if item.is_dir():
                deleted_files += sum(1 for f in item.rglob("*") if f.is_file())
                shutil.rmtree(item)
            elif item.is_file():
                item.unlink()
                deleted_files += 1

    logger.info(
        "Reset: %d garments, %d faces, %d users, %d files deleted",
        n_garments, n_faces, n_users, deleted_files,
    )

    return ResetSummary(
        deleted_garments=n_garments,
        deleted_faces=n_faces,
        deleted_users=n_users,
        deleted_files=deleted_files,
    )


def _collect_images(dirs: list[Path]) -> list[Path]:
    paths: list[Path] = []
    for d in dirs:
        if not d.is_dir():
            logger.warning("Resource directory not found, skipping: %s", d)
            continue
        for p in sorted(d.iterdir()):
            if p.is_file() and p.suffix.lower() in IMAGE_EXTENSIONS:
                paths.append(p)
    return paths


async def seed_wardrobe(
    db: AsyncSession,
    garment_service: GarmentService,
    mode: Literal["reset", "add"] = "add",
    resource_dirs: list[Path] | None = None,
    progress_cb: Callable[[str], None] | None = None,
) -> SeedSummary:
    """Seed preset garment images into the unknown user's wardrobe.

    Each image is analyzed (LLM), saved to DB, and has a ghost mannequin
    generated. Per-image failures are recorded and do not abort the batch.

    Note: `add` mode is not idempotent — rerunning creates duplicates.
    """
    dirs = resource_dirs if resource_dirs is not None else DEFAULT_RESOURCE_DIRS
    summary = SeedSummary(mode=mode)

    if mode == "reset":
        summary.reset_summary = await reset_all_data(db)

    paths = _collect_images(dirs)
    total = len(paths)
    if total == 0:
        if progress_cb:
            progress_cb("No images found in resource directories.")
        return summary

    for idx, path in enumerate(paths, 1):
        label = f"[{idx}/{total}] {path.name}"
        if progress_cb:
            progress_cb(f"{label} — analyzing")

        try:
            image_bytes = path.read_bytes()
        except OSError as e:
            summary.errors.append(SeedError(file=str(path), stage="read", message=str(e)))
            continue

        try:
            garments = await garment_service.analyze_and_save(
                db, image_bytes, user_id=None,
            )
        except Exception as e:
            logger.exception("analyze_and_save failed for %s", path)
            summary.errors.append(SeedError(file=str(path), stage="analyze", message=str(e)))
            continue

        summary.images_processed += 1
        if not garments:
            summary.errors.append(
                SeedError(file=str(path), stage="analyze", message="LLM returned 0 garments")
            )
            continue
        summary.garments_created += len(garments)

        for g in garments:
            if progress_cb:
                progress_cb(f"{label} — ghost ({g.category_main}/{g.category_sub})")
            try:
                await garment_service.generate_ghost(db, g.id)
                summary.ghosts_generated += 1
            except Exception as e:
                logger.exception("generate_ghost failed for garment %s", g.id)
                summary.errors.append(
                    SeedError(file=str(path), stage="ghost", message=str(e))
                )

    return summary
