"""Measurements router - create, query, export measurements."""
import csv
import io
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi.responses import StreamingResponse
from sqlalchemy import and_, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from src.backend.database import get_db
from src.backend.api.middleware.api_key import (
    get_current_user,
    require_measurement_tec,
    require_metrologist,
)
from src.backend.models.orm.measurement import Measurement
from src.backend.models.orm.recipe import RecipeVersion
from src.backend.models.orm.setting import SystemSetting
from src.backend.models.orm.user import User
from src.backend.models.api.measurement import (
    MeasurementBatchCreate,
    MeasurementCreate,
    MeasurementListResponse,
    MeasurementResponse,
)
from src.backend.services.measurement_service import save_measurement

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


@router.post("/", response_model=MeasurementResponse)
async def create_measurement(
    data: MeasurementCreate,
    user: User = Depends(require_measurement_tec),
    db: AsyncSession = Depends(get_db),
):
    """Create a single measurement with auto-calculated pass/fail."""
    try:
        measurement = await save_measurement(
            db=db,
            subtask_id=data.subtask_id,
            version_id=data.version_id,
            measured_by=user.id,
            value=data.value,
            lot_number=data.lot_number,
            serial_number=data.serial_number,
            input_method=data.input_method,
        )
        return MeasurementResponse.model_validate(measurement)
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e),
        )


@router.post("/batch", response_model=list[MeasurementResponse])
async def create_measurement_batch(
    data: MeasurementBatchCreate,
    user: User = Depends(require_measurement_tec),
    db: AsyncSession = Depends(get_db),
):
    """Create multiple measurements in a batch."""
    measurements = []
    try:
        for measurement_data in data.measurements:
            measurement = await save_measurement(
                db=db,
                subtask_id=measurement_data.subtask_id,
                version_id=measurement_data.version_id,
                measured_by=user.id,
                value=measurement_data.value,
                lot_number=measurement_data.lot_number,
                serial_number=measurement_data.serial_number,
                input_method=measurement_data.input_method,
            )
            measurements.append(measurement)
        return [MeasurementResponse.model_validate(m) for m in measurements]
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e),
        )


def _build_measurement_filters(
    recipe_id: int | None,
    version_id: int | None,
    subtask_id: int | None,
    measured_by: int | None,
    lot_number: str | None,
    serial_number: str | None,
    date_from: datetime | None,
    date_to: datetime | None,
    pass_fail: str | None,
) -> list:
    """Build SQLAlchemy filter conditions shared by list and CSV export."""
    filters = []
    if recipe_id is not None:
        # Filter by recipe via subquery on RecipeVersion
        version_ids = select(RecipeVersion.id).where(
            RecipeVersion.recipe_id == recipe_id
        )
        filters.append(Measurement.version_id.in_(version_ids))
    if version_id is not None:
        filters.append(Measurement.version_id == version_id)
    if subtask_id is not None:
        filters.append(Measurement.subtask_id == subtask_id)
    if measured_by is not None:
        filters.append(Measurement.measured_by == measured_by)
    if lot_number is not None:
        filters.append(Measurement.lot_number == lot_number)
    if serial_number is not None:
        filters.append(Measurement.serial_number == serial_number)
    if date_from is not None:
        filters.append(Measurement.measured_at >= date_from)
    if date_to is not None:
        filters.append(Measurement.measured_at <= date_to)
    if pass_fail is not None:
        filters.append(Measurement.pass_fail == pass_fail)
    return filters


@router.get("/", response_model=MeasurementListResponse)
async def get_measurements(
    recipe_id: int | None = Query(None),
    version_id: int | None = Query(None),
    subtask_id: int | None = Query(None),
    measured_by: int | None = Query(None),
    lot_number: str | None = Query(None),
    serial_number: str | None = Query(None),
    date_from: datetime | None = Query(None),
    date_to: datetime | None = Query(None),
    pass_fail: str | None = Query(None, pattern="^(pass|warning|fail)$"),
    page: int = Query(1, ge=1),
    per_page: int = Query(50, ge=1, le=500),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Query measurements with filters and pagination.

    Available to any authenticated user. The MeasurementTec workflow needs
    this endpoint to render `task_complete.html` after running through a
    recipe; the Metrologist dashboard uses the same endpoint with broader
    filters. Measurements carry no PII beyond numeric values + a recipe
    reference, so role-gating beyond authentication isn't justified.
    """
    filters = _build_measurement_filters(
        recipe_id, version_id, subtask_id, measured_by, lot_number,
        serial_number, date_from, date_to, pass_fail,
    )

    # Build query
    query = select(Measurement).where(and_(*filters) if filters else True)

    # Count total
    count_query = select(func.count()).select_from(Measurement).where(
        and_(*filters) if filters else True
    )
    total_result = await db.execute(count_query)
    total = total_result.scalar_one()

    # Paginate
    offset = (page - 1) * per_page
    query = query.order_by(Measurement.measured_at.desc()).limit(per_page).offset(offset)

    result = await db.execute(query)
    measurements = result.scalars().all()

    pages = (total + per_page - 1) // per_page

    return MeasurementListResponse(
        items=[MeasurementResponse.model_validate(m) for m in measurements],
        total=total,
        page=page,
        per_page=per_page,
        pages=pages,
    )


@router.get("/export/csv")
async def export_measurements_csv(
    recipe_id: int | None = Query(None),
    version_id: int | None = Query(None),
    subtask_id: int | None = Query(None),
    measured_by: int | None = Query(None),
    lot_number: str | None = Query(None),
    serial_number: str | None = Query(None),
    date_from: datetime | None = Query(None),
    date_to: datetime | None = Query(None),
    pass_fail: str | None = Query(None, pattern="^(pass|warning|fail)$"),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Export measurements to CSV with configurable delimiter and decimal separator."""
    # Check user has MeasurementTec or Metrologist role
    if not (user.has_role("MeasurementTec") or user.has_role("Metrologist")):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="MeasurementTec or Metrologist role required",
        )

    # Get CSV settings from system_settings
    delimiter_result = await db.execute(
        select(SystemSetting).where(SystemSetting.setting_key == "csv_delimiter")
    )
    delimiter_setting = delimiter_result.scalar_one_or_none()
    delimiter = delimiter_setting.setting_value if delimiter_setting else ","

    decimal_result = await db.execute(
        select(SystemSetting).where(SystemSetting.setting_key == "csv_decimal_separator")
    )
    decimal_setting = decimal_result.scalar_one_or_none()
    decimal_separator = decimal_setting.setting_value if decimal_setting else "."

    filters = _build_measurement_filters(
        recipe_id, version_id, subtask_id, measured_by, lot_number,
        serial_number, date_from, date_to, pass_fail,
    )

    # Query all matching measurements
    query = select(Measurement).where(and_(*filters) if filters else True)
    query = query.order_by(Measurement.measured_at.desc())
    result = await db.execute(query)
    measurements = result.scalars().all()

    # Generate CSV
    output = io.StringIO()
    writer = csv.writer(output, delimiter=delimiter)

    # Header
    writer.writerow([
        "id",
        "subtask_id",
        "version_id",
        "measured_by",
        "value",
        "pass_fail",
        "deviation",
        "lot_number",
        "serial_number",
        "input_method",
        "measured_at",
        "synced_to_csv",
    ])

    # Rows
    for m in measurements:
        # Format decimals with configured separator
        value_str = str(m.value).replace(".", decimal_separator)
        deviation_str = (
            str(m.deviation).replace(".", decimal_separator)
            if m.deviation is not None
            else ""
        )

        writer.writerow([
            m.id,
            m.subtask_id,
            m.version_id,
            m.measured_by,
            value_str,
            m.pass_fail,
            deviation_str,
            m.lot_number or "",
            m.serial_number or "",
            m.input_method,
            m.measured_at.isoformat(),
            m.synced_to_csv,
        ])

    # Return as streaming response
    output.seek(0)
    return StreamingResponse(
        iter([output.getvalue()]),
        media_type="text/csv",
        headers={
            "Content-Disposition": f"attachment; filename=measurements_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
        },
    )
