"""Render Templates API — CRUD + .blend file upload/download + material library."""
import uuid
import shutil
from datetime import datetime
from pathlib import Path

from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, status
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update as sql_update, delete as sql_delete
from pydantic import BaseModel

from app.database import get_db
from app.config import settings as app_settings
from app.models.user import User
from app.models.render_template import RenderTemplate
from app.models.output_type import OutputType
from app.models.system_setting import SystemSetting
from app.utils.auth import require_admin_or_pm

router = APIRouter(tags=["render-templates"])

BLEND_DIR = "blend-templates"


def _blend_dir() -> Path:
    d = Path(app_settings.upload_dir) / BLEND_DIR
    d.mkdir(parents=True, exist_ok=True)
    return d


# ── Schemas ──────────────────────────────────────────────────────────────────

class RenderTemplateOut(BaseModel):
    id: str
    name: str
    category_key: str | None
    output_type_id: str | None
    output_type_name: str | None
    blend_file_path: str
    original_filename: str
    target_collection: str
    material_replace_enabled: bool
    lighting_only: bool
    shadow_catcher_enabled: bool
    camera_orbit: bool
    is_active: bool
    created_at: str
    updated_at: str

    model_config = {"from_attributes": True}


class RenderTemplateUpdate(BaseModel):
    name: str | None = None
    category_key: str | None = None
    output_type_id: str | None = None
    target_collection: str | None = None
    material_replace_enabled: bool | None = None
    lighting_only: bool | None = None
    shadow_catcher_enabled: bool | None = None
    camera_orbit: bool | None = None
    is_active: bool | None = None


class MaterialLibraryInfo(BaseModel):
    exists: bool
    filename: str | None = None
    size_bytes: int | None = None
    path: str | None = None


def _to_out(t: RenderTemplate) -> dict:
    ot_name = None
    if t.output_type:
        ot_name = t.output_type.name
    return {
        "id": str(t.id),
        "name": t.name,
        "category_key": t.category_key,
        "output_type_id": str(t.output_type_id) if t.output_type_id else None,
        "output_type_name": ot_name,
        "blend_file_path": t.blend_file_path,
        "original_filename": t.original_filename,
        "target_collection": t.target_collection,
        "material_replace_enabled": t.material_replace_enabled,
        "lighting_only": t.lighting_only,
        "shadow_catcher_enabled": t.shadow_catcher_enabled,
        "camera_orbit": t.camera_orbit,
        "is_active": t.is_active,
        "created_at": t.created_at.isoformat() if t.created_at else "",
        "updated_at": t.updated_at.isoformat() if t.updated_at else "",
    }


# ── CRUD Endpoints ───────────────────────────────────────────────────────────

@router.get("/render-templates", response_model=list[RenderTemplateOut])
async def list_render_templates(
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(RenderTemplate).order_by(RenderTemplate.created_at.desc())
    )
    return [_to_out(t) for t in result.scalars().all()]


@router.post("/render-templates", response_model=RenderTemplateOut, status_code=status.HTTP_201_CREATED)
async def create_render_template(
    name: str = Form(...),
    file: UploadFile = File(...),
    category_key: str | None = Form(None),
    output_type_id: str | None = Form(None),
    target_collection: str = Form("Product"),
    material_replace_enabled: bool = Form(False),
    lighting_only: bool = Form(False),
    shadow_catcher_enabled: bool = Form(False),
    camera_orbit: bool = Form(True),
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    if not file.filename or not file.filename.endswith(".blend"):
        raise HTTPException(400, detail="File must be a .blend file")

    # Normalise empty strings from form data to None
    if category_key == "" or category_key == "null":
        category_key = None
    if output_type_id == "" or output_type_id == "null":
        output_type_id = None

    template_id = uuid.uuid4()
    blend_path = _blend_dir() / f"{template_id}.blend"

    with open(blend_path, "wb") as f:
        shutil.copyfileobj(file.file, f)

    ot_uuid = uuid.UUID(output_type_id) if output_type_id else None

    tmpl = RenderTemplate(
        id=template_id,
        name=name,
        category_key=category_key,
        output_type_id=ot_uuid,
        blend_file_path=str(blend_path),
        original_filename=file.filename,
        target_collection=target_collection,
        material_replace_enabled=material_replace_enabled,
        lighting_only=lighting_only,
        shadow_catcher_enabled=shadow_catcher_enabled,
        camera_orbit=camera_orbit,
    )
    db.add(tmpl)
    await db.commit()
    await db.refresh(tmpl)

    # Eagerly load output_type for response
    if ot_uuid:
        ot = await db.get(OutputType, ot_uuid)
        tmpl.output_type = ot

    return _to_out(tmpl)


@router.patch("/render-templates/{template_id}", response_model=RenderTemplateOut)
async def update_render_template(
    template_id: uuid.UUID,
    body: RenderTemplateUpdate,
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(RenderTemplate).where(RenderTemplate.id == template_id))
    tmpl = result.scalar_one_or_none()
    if not tmpl:
        raise HTTPException(404, detail="Render template not found")

    updates = body.model_dump(exclude_unset=True)

    # Normalise empty strings to None for nullable fields
    if "category_key" in updates and updates["category_key"] in ("", "null"):
        updates["category_key"] = None
    if "output_type_id" in updates:
        val = updates["output_type_id"]
        if val in ("", "null", None):
            updates["output_type_id"] = None
        else:
            updates["output_type_id"] = uuid.UUID(val)

    if updates:
        updates["updated_at"] = datetime.utcnow()
        await db.execute(
            sql_update(RenderTemplate)
            .where(RenderTemplate.id == template_id)
            .values(**updates)
        )
        await db.commit()
        await db.refresh(tmpl)

    return _to_out(tmpl)


@router.delete("/render-templates/{template_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_render_template(
    template_id: uuid.UUID,
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(RenderTemplate).where(RenderTemplate.id == template_id))
    tmpl = result.scalar_one_or_none()
    if not tmpl:
        raise HTTPException(404, detail="Render template not found")

    # Delete .blend file
    blend_path = Path(tmpl.blend_file_path)
    if blend_path.exists():
        blend_path.unlink(missing_ok=True)

    await db.execute(sql_delete(RenderTemplate).where(RenderTemplate.id == template_id))
    await db.commit()


@router.post("/render-templates/{template_id}/upload", response_model=RenderTemplateOut)
async def upload_blend_file(
    template_id: uuid.UUID,
    file: UploadFile = File(...),
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    """Re-upload a .blend file for an existing template."""
    if not file.filename or not file.filename.endswith(".blend"):
        raise HTTPException(400, detail="File must be a .blend file")

    result = await db.execute(select(RenderTemplate).where(RenderTemplate.id == template_id))
    tmpl = result.scalar_one_or_none()
    if not tmpl:
        raise HTTPException(404, detail="Render template not found")

    blend_path = _blend_dir() / f"{template_id}.blend"

    # Remove old file if path changed
    old_path = Path(tmpl.blend_file_path)
    if old_path.exists() and old_path != blend_path:
        old_path.unlink(missing_ok=True)

    with open(blend_path, "wb") as f:
        shutil.copyfileobj(file.file, f)

    await db.execute(
        sql_update(RenderTemplate)
        .where(RenderTemplate.id == template_id)
        .values(
            blend_file_path=str(blend_path),
            original_filename=file.filename,
            updated_at=datetime.utcnow(),
        )
    )
    await db.commit()
    await db.refresh(tmpl)
    return _to_out(tmpl)


@router.get("/render-templates/{template_id}/download")
async def download_blend_file(
    template_id: uuid.UUID,
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(RenderTemplate).where(RenderTemplate.id == template_id))
    tmpl = result.scalar_one_or_none()
    if not tmpl:
        raise HTTPException(404, detail="Render template not found")

    blend_path = Path(tmpl.blend_file_path)
    if not blend_path.exists():
        raise HTTPException(404, detail=".blend file not found on disk")

    return FileResponse(
        path=str(blend_path),
        filename=tmpl.original_filename,
        media_type="application/octet-stream",
    )


# ── Material Library ─────────────────────────────────────────────────────────

MATERIAL_LIBRARY_FILENAME = "material_library.blend"


async def _save_setting(db: AsyncSession, key: str, value: str) -> None:
    result = await db.execute(
        sql_update(SystemSetting)
        .where(SystemSetting.key == key)
        .values(value=value, updated_at=datetime.utcnow())
    )
    if result.rowcount == 0:
        db.add(SystemSetting(key=key, value=value, updated_at=datetime.utcnow()))


@router.post("/admin/settings/material-library", response_model=MaterialLibraryInfo)
async def upload_material_library(
    file: UploadFile = File(...),
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    if not file.filename or not file.filename.endswith(".blend"):
        raise HTTPException(400, detail="File must be a .blend file")

    lib_path = _blend_dir() / MATERIAL_LIBRARY_FILENAME
    with open(lib_path, "wb") as f:
        shutil.copyfileobj(file.file, f)

    await _save_setting(db, "material_library_path", str(lib_path))
    await db.commit()

    return MaterialLibraryInfo(
        exists=True,
        filename=file.filename,
        size_bytes=lib_path.stat().st_size,
        path=str(lib_path),
    )


@router.get("/admin/settings/material-library", response_model=MaterialLibraryInfo)
async def get_material_library(
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(SystemSetting).where(SystemSetting.key == "material_library_path")
    )
    row = result.scalar_one_or_none()
    path_str = row.value if row else ""

    if path_str and Path(path_str).exists():
        p = Path(path_str)
        return MaterialLibraryInfo(
            exists=True,
            filename=p.name,
            size_bytes=p.stat().st_size,
            path=path_str,
        )
    return MaterialLibraryInfo(exists=False)


@router.delete("/admin/settings/material-library", status_code=status.HTTP_204_NO_CONTENT)
async def delete_material_library(
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(SystemSetting).where(SystemSetting.key == "material_library_path")
    )
    row = result.scalar_one_or_none()
    if row and row.value:
        p = Path(row.value)
        if p.exists():
            p.unlink(missing_ok=True)

    await _save_setting(db, "material_library_path", "")
    await db.commit()
