from __future__ import annotations

from fastapi import APIRouter, Body, Depends, HTTPException, Query, status
from sqlalchemy import desc, select
from sqlalchemy.orm import Session

from app.api.routes.api.deps import require_csrf, require_session
from app.dependencies import get_db
from app.models.location import Location
from app.models.poo import PooRecord
from app.models.public_ip import PublicIPHistory, PublicIPState
from app.schemas.data import (
    LocationRecord,
    LocationUpdateRequest,
    LocationsResponse,
    PooRecord as PooRecordSchema,
    PooResponse,
    PooUpdateRequest,
    PublicIPHistorySchema,
    PublicIPResponse,
    PublicIPStateSchema,
)
from app.services.auth import AuthenticatedSession
from app.services.location import delete_location, update_location
from app.services.poo import delete_poo_record, update_poo_record

router = APIRouter(prefix="/api", tags=["api-data"])


@router.get("/locations", response_model=LocationsResponse)
def get_locations(
    limit: int = Query(default=1000, ge=1, le=5000),
    offset: int = Query(default=0, ge=0),
    start: str | None = Query(default=None),
    end: str | None = Query(default=None),
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
) -> LocationsResponse:
    """
    Return location records with optional time-window filtering and pagination.

    - ``start`` / ``end`` are ISO8601 strings; filtering is **inclusive** on both bounds.
    - Results are ordered by ``datetime`` ascending.
    - ``limit`` is capped at 5000 to prevent full-table exports.
    """
    stmt = select(Location)

    if start is not None:
        stmt = stmt.where(Location.datetime >= start)
    if end is not None:
        stmt = stmt.where(Location.datetime <= end)

    stmt = stmt.order_by(Location.datetime).offset(offset).limit(limit)

    rows = db.execute(stmt).scalars().all()

    items = [
        LocationRecord(
            person=row.person,
            datetime=row.datetime,
            latitude=row.latitude,
            longitude=row.longitude,
            altitude=row.altitude,
        )
        for row in rows
    ]

    return LocationsResponse(items=items, limit=limit, offset=offset)


@router.get("/poo", response_model=PooResponse)
def get_poo(
    limit: int = Query(default=100, ge=1, le=1000),
    offset: int = Query(default=0, ge=0),
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
) -> PooResponse:
    """
    Return poo records ordered by timestamp descending (most recent first).

    ``limit`` is capped at 1000 to prevent full-table exports.
    """
    stmt = (
        select(PooRecord)
        .order_by(desc(PooRecord.timestamp))
        .offset(offset)
        .limit(limit)
    )

    rows = db.execute(stmt).scalars().all()

    items = [
        PooRecordSchema(
            timestamp=row.timestamp,
            status=row.status,
            latitude=row.latitude,
            longitude=row.longitude,
        )
        for row in rows
    ]

    return PooResponse(items=items, limit=limit, offset=offset)


@router.get("/public-ip", response_model=PublicIPResponse)
def get_public_ip(
    limit: int = Query(default=100, ge=1, le=1000),
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
) -> PublicIPResponse:
    """
    Return the current public IP state and recent history.

    - ``state`` is ``null`` if no IP check has been performed yet.
    - ``history`` is ordered by ``observed_at`` descending (most recent first).
    - ``limit`` applies to the history list and is capped at 1000.
    """
    state_row = db.execute(
        select(PublicIPState).where(PublicIPState.id == 1).limit(1)
    ).scalar_one_or_none()

    history_rows = db.execute(
        select(PublicIPHistory).order_by(desc(PublicIPHistory.observed_at)).limit(limit)
    ).scalars().all()

    state = PublicIPStateSchema.model_validate(state_row) if state_row is not None else None
    history = [PublicIPHistorySchema.model_validate(row) for row in history_rows]

    return PublicIPResponse(state=state, history=history)


# ---------------------------------------------------------------------------
# PATCH /api/locations/{person}/{datetime}
# ---------------------------------------------------------------------------


@router.patch("/locations/{person}/{datetime}", response_model=LocationRecord)
def patch_location(
    person: str,
    datetime: str,
    body: LocationUpdateRequest = Body(default=LocationUpdateRequest()),
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
    _csrf: None = Depends(require_csrf),
) -> LocationRecord:
    """
    Update the non-PK fields of a single location record.

    - ``person`` and ``datetime`` identify the row (composite PK) and are immutable.
    - Only ``latitude``, ``longitude``, and ``altitude`` may be updated.
    - Omitted body fields are left unchanged.
    - Returns **404** if the PK does not exist.
    """
    row = update_location(
        db,
        person,
        datetime,
        latitude=body.latitude,
        longitude=body.longitude,
        altitude=body.altitude,
    )
    if row is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="location record not found",
        )
    return LocationRecord(
        person=row.person,
        datetime=row.datetime,
        latitude=row.latitude,
        longitude=row.longitude,
        altitude=row.altitude,
    )


# ---------------------------------------------------------------------------
# DELETE /api/locations/{person}/{datetime}
# ---------------------------------------------------------------------------


@router.delete(
    "/locations/{person}/{datetime}",
    status_code=status.HTTP_204_NO_CONTENT,
    response_model=None,
)
def delete_location_record(
    person: str,
    datetime: str,
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
    _csrf: None = Depends(require_csrf),
) -> None:
    """
    Delete the single location record identified by its composite PK.

    - Exactly one row is deleted; **404** if the PK does not exist.
    - No batch delete / truncate path is available.
    """
    deleted = delete_location(db, person, datetime)
    if not deleted:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="location record not found",
        )


# ---------------------------------------------------------------------------
# PATCH /api/poo/{timestamp}
# ---------------------------------------------------------------------------


@router.patch("/poo/{timestamp}", response_model=PooRecordSchema)
def patch_poo(
    timestamp: str,
    body: PooUpdateRequest = Body(default=PooUpdateRequest()),
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
    _csrf: None = Depends(require_csrf),
) -> PooRecordSchema:
    """
    Update the non-PK fields of a single poo record.

    - ``timestamp`` is the PK and is immutable.
    - Only ``status``, ``latitude``, and ``longitude`` may be updated.
    - Omitted body fields are left unchanged.
    - Returns **404** if the PK does not exist.
    """
    row = update_poo_record(
        db,
        timestamp,
        status=body.status,
        latitude=body.latitude,
        longitude=body.longitude,
    )
    if row is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="poo record not found",
        )
    return PooRecordSchema(
        timestamp=row.timestamp,
        status=row.status,
        latitude=row.latitude,
        longitude=row.longitude,
    )


# ---------------------------------------------------------------------------
# DELETE /api/poo/{timestamp}
# ---------------------------------------------------------------------------


@router.delete(
    "/poo/{timestamp}",
    status_code=status.HTTP_204_NO_CONTENT,
    response_model=None,
)
def delete_poo(
    timestamp: str,
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
    _csrf: None = Depends(require_csrf),
) -> None:
    """
    Delete the single poo record identified by its PK.

    - Exactly one row is deleted; **404** if the PK does not exist.
    - No batch delete / truncate path is available.
    """
    deleted = delete_poo_record(db, timestamp)
    if not deleted:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="poo record not found",
        )
