from typing import Optional
from uuid import UUID

from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.plots.schemas.requests import (
    ChangePlotStatusRequest,
    CreatePlotRequest,
    UpdatePlotRequest,
)
from src.apps.plots.schemas.responses import PlotResponse
from src.apps.plots.services.plot_service import PlotService
from src.core.constants import UserRole
from src.core.dependencies import require_roles
from src.core.exceptions import NotFoundError
from src.core.schemas.response import paginated, success
from src.database.session import get_db

router = APIRouter(prefix="/plots", tags=["Plots"])


def _serialize_plot(plot) -> dict:
    """Serialize a Plot ORM object to a dict, enriching with joined relationship fields."""
    data = PlotResponse.model_validate(plot).model_dump()
    # Enrich with denormalized names from loaded relationships
    if plot.section is not None:
        data["section_code"] = plot.section.code
    if plot.plot_type is not None:
        data["plot_type_name"] = plot.plot_type.name
    return data


@router.get("", response_model=dict)
async def list_plots(
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=200),
    section_id: UUID = Query(None),
    status: str = Query(None),
    search: Optional[str] = Query(None),
    current_user=Depends(
        require_roles(
            UserRole.ADMINISTRATOR,
            UserRole.MANAGER,
            UserRole.STAFF,
            UserRole.VIEW_ONLY,
        )
    ),
    db: AsyncSession = Depends(get_db),
):
    """List plots with optional filtering by section and/or status."""
    service = PlotService(db)
    plots, total = await service.list_plots(
        tenant_id=current_user.tenant_id,
        skip=skip,
        limit=limit,
        section_id=section_id,
        status=status,
        search=search,
    )
    return paginated(
        items=[_serialize_plot(p) for p in plots],
        total=total,
        page=(skip // limit) + 1 if limit else 1,
        page_size=limit,
    )


@router.post("", response_model=dict, status_code=201)
async def create_plot(
    body: CreatePlotRequest,
    current_user=Depends(
        require_roles(UserRole.ADMINISTRATOR, UserRole.MANAGER)
    ),
    db: AsyncSession = Depends(get_db),
):
    """Create a new plot."""
    service = PlotService(db)
    plot = await service.create(
        tenant_id=current_user.tenant_id,
        data=body.model_dump(),
    )
    return success(
        data=_serialize_plot(plot),
        message="Plot created",
    )


@router.get("/{plot_id}", response_model=dict)
async def get_plot(
    plot_id: UUID,
    current_user=Depends(
        require_roles(
            UserRole.ADMINISTRATOR,
            UserRole.MANAGER,
            UserRole.STAFF,
            UserRole.VIEW_ONLY,
        )
    ),
    db: AsyncSession = Depends(get_db),
):
    """Get a single plot by ID."""
    service = PlotService(db)
    plot = await service.get_by_id(plot_id, current_user.tenant_id)
    if not plot:
        raise NotFoundError("Plot not found")
    return success(data=_serialize_plot(plot))


@router.patch("/{plot_id}", response_model=dict)
async def update_plot(
    plot_id: UUID,
    body: UpdatePlotRequest,
    current_user=Depends(
        require_roles(UserRole.ADMINISTRATOR, UserRole.MANAGER)
    ),
    db: AsyncSession = Depends(get_db),
):
    """Update plot metadata."""
    service = PlotService(db)
    plot = await service.update(
        plot_id=plot_id,
        tenant_id=current_user.tenant_id,
        data=body.model_dump(exclude_none=True),
    )
    return success(
        data=_serialize_plot(plot),
        message="Plot updated",
    )


@router.delete("/{plot_id}", status_code=204)
async def delete_plot(
    plot_id: UUID,
    current_user=Depends(require_roles(UserRole.ADMINISTRATOR)),
    db: AsyncSession = Depends(get_db),
):
    """Delete a plot (Administrator only; plot must be vacant)."""
    service = PlotService(db)
    await service.delete(plot_id, current_user.tenant_id)


@router.patch("/{plot_id}/status", response_model=dict)
async def change_plot_status(
    plot_id: UUID,
    body: ChangePlotStatusRequest,
    current_user=Depends(
        require_roles(
            UserRole.ADMINISTRATOR,
            UserRole.MANAGER,
            UserRole.STAFF,
        )
    ),
    db: AsyncSession = Depends(get_db),
):
    """Change the status of a plot (vacant / reserved / occupied / hold)."""
    service = PlotService(db)
    plot = await service.change_status(
        plot_id=plot_id,
        tenant_id=current_user.tenant_id,
        status=body.status,
    )
    return success(
        data=_serialize_plot(plot),
        message=f"Plot status updated to '{plot.status}'",
    )
