from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
import logging

from sqlalchemy import delete, desc, insert, select
from sqlalchemy.orm import Session

from app.config import Settings
from app.integrations.homeassistant import (
    HomeAssistantClient,
    HomeAssistantConfigError,
    HomeAssistantRequestError,
)
from app.models.poo import PooRecord
from app.schemas.poo import PooRecordRequest

logger = logging.getLogger(__name__)


@dataclass(slots=True)
class LatestPooRecord:
    timestamp: str
    status: str
    latitude: float
    longitude: float


def _parse_required_float(value: str, field_name: str) -> float:
    try:
        return float(value)
    except (TypeError, ValueError) as exc:
        raise ValueError(f"Invalid numeric value for {field_name}") from exc


def _utc_now_minute_precision() -> str:
    now = datetime.now(timezone.utc).replace(second=0, microsecond=0)
    return now.strftime("%Y-%m-%dT%H:%MZ")


def record_poo(
    session: Session,
    payload: PooRecordRequest,
    *,
    settings: Settings,
    homeassistant_client: HomeAssistantClient,
) -> None:
    stmt = insert(PooRecord).prefix_with("OR IGNORE").values(
        timestamp=_utc_now_minute_precision(),
        status=payload.status,
        latitude=_parse_required_float(payload.latitude, "latitude"),
        longitude=_parse_required_float(payload.longitude, "longitude"),
    )
    session.execute(stmt)
    session.commit()

    try:
        publish_latest_poo_status(
            session=session,
            settings=settings,
            homeassistant_client=homeassistant_client,
        )
    except (HomeAssistantConfigError, HomeAssistantRequestError) as exc:
        logger.warning("Failed to publish latest poo status to Home Assistant: %s", exc)

    if settings.poo_webhook_id:
        try:
            homeassistant_client.trigger_webhook(
                webhook_id=settings.poo_webhook_id,
                body={"status": payload.status},
            )
        except (HomeAssistantConfigError, HomeAssistantRequestError) as exc:
            logger.warning("Failed to trigger poo webhook on Home Assistant: %s", exc)


def update_poo_record(
    session: Session,
    timestamp_pk: str,
    *,
    status: str | None,
    latitude: float | None,
    longitude: float | None,
) -> PooRecord | None:
    """Update non-PK fields of a single poo record row.

    Returns the updated ORM object, or ``None`` if the PK does not exist.
    The ``timestamp`` PK is immutable and must not be passed as an update field.
    Only fields with a non-``None`` value are written.
    """
    row = session.execute(
        select(PooRecord).where(PooRecord.timestamp == timestamp_pk)
    ).scalar_one_or_none()

    if row is None:
        return None

    if status is not None:
        row.status = status
    if latitude is not None:
        row.latitude = latitude
    if longitude is not None:
        row.longitude = longitude

    session.commit()
    session.refresh(row)
    return row


def delete_poo_record(session: Session, timestamp_pk: str) -> bool:
    """Delete the single poo record row identified by its PK.

    Returns ``True`` if exactly one row was deleted, ``False`` if the PK did
    not exist (caller should raise 404).  The DELETE is scoped to the exact PK
    — no batch/truncate path exists.
    """
    result = session.execute(
        delete(PooRecord).where(PooRecord.timestamp == timestamp_pk)
    )
    session.commit()
    return result.rowcount == 1


def get_latest_poo_record(session: Session) -> LatestPooRecord | None:
    stmt = select(PooRecord).order_by(desc(PooRecord.timestamp)).limit(1)
    record = session.execute(stmt).scalar_one_or_none()
    if record is None:
        logger.info("No poo record is available yet")
        return None
    return LatestPooRecord(
        timestamp=record.timestamp,
        status=record.status,
        latitude=record.latitude,
        longitude=record.longitude,
    )


def publish_latest_poo_status(
    *,
    session: Session,
    settings: Settings,
    homeassistant_client: HomeAssistantClient,
) -> LatestPooRecord | None:
    latest = get_latest_poo_record(session)
    if latest is None:
        logger.info("Skipping Home Assistant poo sensor publish because no poo record exists yet")
        return None

    record_time = datetime.fromisoformat(latest.timestamp.replace("Z", "+00:00")).astimezone()

    homeassistant_client.publish_sensor(
        entity_id=settings.poo_sensor_entity_name,
        state=latest.status,
        attributes={
            "last_poo": record_time.strftime("%a | %Y-%m-%d | %H:%M"),
            "friendly_name": settings.poo_sensor_friendly_name,
        },
    )
    return latest
