from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse, PlainTextResponse
from sqlalchemy.ext.asyncio import AsyncSession
from auth.models import TokenPayload
from auth.dependencies import require_permission
from builder.models import (
    BuiltMelodyCreate,
    BuiltMelodyUpdate,
    BuiltMelodyInDB,
    BuiltMelodyListResponse,
)
from builder import service
from database.postgres import get_pg_session
from shared.audit import log_action

router = APIRouter(prefix="/api/builder/melodies", tags=["builder"])


@router.get("", response_model=BuiltMelodyListResponse)
async def list_built_melodies(
    _user: TokenPayload = Depends(require_permission("melodies", "view")),
):
    melodies = await service.list_built_melodies()
    return BuiltMelodyListResponse(melodies=melodies, total=len(melodies))


@router.get("/for-melody/{firestore_melody_id}")
async def get_for_firestore_melody(
    firestore_melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "view")),
):
    """Get the built melody assigned to a given Firestore melody ID. Returns null if none found."""
    result = await service.get_built_melody_for_firestore_id(firestore_melody_id)
    if result is None:
        return None
    return result.model_dump()


@router.get("/generate-builtin-list")
async def generate_builtin_list(
    _user: TokenPayload = Depends(require_permission("melodies", "view")),
):
    """Generate a C++ header with PROGMEM arrays for all is_builtin archetypes."""
    code = await service.generate_builtin_list()
    return PlainTextResponse(content=code, media_type="text/plain")


@router.get("/{melody_id}", response_model=BuiltMelodyInDB)
async def get_built_melody(
    melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "view")),
):
    return await service.get_built_melody(melody_id)


@router.post("", response_model=BuiltMelodyInDB, status_code=201)
async def create_built_melody(
    body: BuiltMelodyCreate,
    _user: TokenPayload = Depends(require_permission("melodies", "edit")),
    db: AsyncSession = Depends(get_pg_session),
):
    melody = await service.create_built_melody(body)
    await log_action(db, _user.sub, _user.name or _user.email, "CREATE", "archetype",
                     str(melody.id), melody.name or str(melody.id))
    return melody


@router.put("/{melody_id}", response_model=BuiltMelodyInDB)
async def update_built_melody(
    melody_id: str,
    body: BuiltMelodyUpdate,
    _user: TokenPayload = Depends(require_permission("melodies", "edit")),
    db: AsyncSession = Depends(get_pg_session),
):
    old = await service.get_built_melody(melody_id)
    melody = await service.update_built_melody(melody_id, body)
    _SKIP = {"updated_at", "id", "steps", "builtin_code"}
    changes = {
        k: {"old": getattr(old, k, None), "new": getattr(melody, k, None)}
        for k in body.model_fields_set
        if k not in _SKIP and getattr(old, k, None) != getattr(melody, k, None)
    }
    await log_action(db, _user.sub, _user.name or _user.email, "UPDATE", "archetype",
                     melody_id, melody.name or melody_id, changes=changes or None)
    return melody


@router.delete("/{melody_id}", status_code=204)
async def delete_built_melody(
    melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "delete")),
    db: AsyncSession = Depends(get_pg_session),
):
    melody = await service.get_built_melody(melody_id)
    await service.delete_built_melody(melody_id)
    await log_action(db, _user.sub, _user.name or _user.email, "DELETE", "archetype",
                     melody_id, melody.name if melody else melody_id)


@router.post("/{melody_id}/toggle-builtin", response_model=BuiltMelodyInDB)
async def toggle_builtin(
    melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "edit")),
):
    """Toggle the is_builtin flag for an archetype."""
    return await service.toggle_builtin(melody_id)


@router.post("/{melody_id}/build-binary", response_model=BuiltMelodyInDB)
async def build_binary(
    melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "edit")),
):
    """Build the .bsm binary file from the stored steps."""
    return await service.build_binary(melody_id)


@router.post("/{melody_id}/build-builtin", response_model=BuiltMelodyInDB)
async def build_builtin_code(
    melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "edit")),
):
    """Generate PROGMEM C code and store it in the database."""
    return await service.build_builtin_code(melody_id)


@router.get("/{melody_id}/download")
async def download_binary(
    melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "view")),
):
    """Download the .bsm binary file."""
    path = await service.get_binary_path(melody_id)
    if not path:
        raise HTTPException(status_code=404, detail="Binary not built yet for this melody")

    melody = await service.get_built_melody(melody_id)
    filename = f"{melody.pid or melody.name}.bsm"

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


@router.post("/{melody_id}/assign", response_model=BuiltMelodyInDB)
async def assign_to_melody(
    melody_id: str,
    firestore_melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "edit")),
):
    """Mark this built melody as assigned to a Firestore melody."""
    return await service.assign_to_melody(melody_id, firestore_melody_id)


@router.post("/{melody_id}/unassign", response_model=BuiltMelodyInDB)
async def unassign_from_melody(
    melody_id: str,
    firestore_melody_id: str,
    _user: TokenPayload = Depends(require_permission("melodies", "edit")),
):
    """Remove a Firestore melody assignment from this built melody."""
    return await service.unassign_from_melody(melody_id, firestore_melody_id)
