"""
Station work: who works at which station, and what a station operator or QA inspector
sees there (chassis at the station, the queue coming to it, parts, checksheets).

Access rule used throughout: a user assigned to a station (station_user_assignment) may
work there. A user with no assignment for that role falls back to module permissions
(PRODUCTION for operators, QA for inspectors), which keeps supervisors and admins working.
"""
from datetime import datetime, timezone, timedelta, date
from typing import List, Optional
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy import select, func, desc
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.database import get_db
from app.core.security import get_current_user, require_permission, user_has_permission
from app.models.core_models import LoginUsers
from app.models.rm_models import StationMaster, RmMaster
from app.models.yard_models import ChassisMaster, VehicleModelMaster
from app.models.sales_models import CustomerMaster
from app.models.production_models import ChassisStationLog, ProductionHold
from app.models.procurement_models import ChassisBomSnapshot
from app.models.qa_models import QaStationTemplateMap, QaChecksheetTemplate, QaChecksheetInstance
from app.models.station_work_models import StationUserAssignment, ChassisStationHandoff
from app.modules.station_work import flow

router = APIRouter()

ROLES = ('OPERATOR', 'QA')
ROLE_MODULE = {'OPERATOR': 'PRODUCTION', 'QA': 'QA'}


def _now():
    return datetime.now(timezone.utc)


def _age_days(cm) -> Optional[int]:
    return (date.today() - cm.arrival_date).days if cm.arrival_date else None


def _elapsed(start: Optional[datetime]) -> Optional[int]:
    return max(0, int((_now() - start).total_seconds() / 60)) if start else None


# ── Access helpers (also used by production scan and QA inspection) ─────────────
async def assigned_station_ids(db: AsyncSession, user_id: UUID, role: str) -> set:
    rows = (await db.execute(
        select(StationUserAssignment.station_id).where(
            StationUserAssignment.user_id == user_id,
            StationUserAssignment.role == role,
            StationUserAssignment.is_active == True,  # noqa: E712
        )
    )).scalars().all()
    return set(rows)


async def ensure_station_access(db: AsyncSession, user: LoginUsers, station_id: UUID, role: str) -> None:
    """Raise 403 if the user is assigned to stations for this role but not to this one,
    or has no assignment and no module permission either."""
    if user.role_code == 'ADMIN':
        return
    if user.role_code == 'QA_SUPERVISOR' and role == 'QA':
        return
    assigned = await assigned_station_ids(db, user.user_id, role)
    if assigned:
        if station_id not in assigned:
            raise HTTPException(403, "You are not assigned to this station")
        return
    if not await user_has_permission(db, user, ROLE_MODULE[role], 'view'):
        raise HTTPException(403, "You are not assigned to any station")


# ── Schemas ─────────────────────────────────────────────────────────────────────
class StationBrief(BaseModel):
    station_id: UUID
    station_code: str
    station_name: str
    sequence_no: Optional[int] = None
    requires_qa: bool = False
    cycle_time_min: Optional[int] = None


class MyStationsOut(BaseModel):
    role: str
    supervisor_mode: bool  # True = not assigned anywhere, seeing all stations through module permission
    stations: List[StationBrief]


class AssignmentOut(BaseModel):
    assignment_id: UUID
    user_id: UUID
    user_name: str
    employee_code: Optional[str] = None
    user_role_code: Optional[str] = None
    station_id: UUID
    station_name: str
    station_code: str
    sequence_no: Optional[int] = None
    role: str
    is_active: bool


class AssignmentCreate(BaseModel):
    user_id: UUID
    station_id: UUID
    role: str = Field(..., pattern='^(OPERATOR|QA)$')


class UserBrief(BaseModel):
    user_id: UUID
    name: str
    employee_code: Optional[str] = None
    role_code: Optional[str] = None


class BoardChassis(BaseModel):
    chassis_id: UUID
    chassis_no: str
    model_name: Optional[str] = None
    hold_status: str = 'NONE'
    since: Optional[datetime] = None
    elapsed_minutes: Optional[int] = None
    qa_status: str = 'NOT_STARTED'  # NOT_STARTED / IN_PROGRESS / PASSED / FAILED (this station)
    age_days: Optional[int] = None   # days since the chassis arrived at the plant
    handoff_status: Optional[str] = None  # AWAITING_QA / QA_PASSED / RELEASED / RECEIVED
    next_station_name: Optional[str] = None


class PartRow(BaseModel):
    rm_id: UUID
    name: str
    part_no: Optional[str] = None
    unit: Optional[str] = None
    quantity: Optional[float] = None
    consumed_qty: Optional[float] = None


class ChecksheetBrief(BaseModel):
    template_id: UUID
    template_code: str
    template_name: str
    is_mandatory: bool = True


class StationBoardOut(BaseModel):
    station: StationBrief
    description: Optional[str] = None
    at_station: List[BoardChassis]
    queue: List[BoardChassis]          # coming next: released to this station by the previous one
    awaiting_qa: List[BoardChassis]     # work done here, waiting for QA to pass and release it
    completed_here: List[BoardChassis]  # passed on from here in the last 7 days
    checksheets: List[ChecksheetBrief]
    parts: List[PartRow]               # parts normally fitted at this station


class ChassisAtStationOut(BaseModel):
    chassis_id: UUID
    chassis_no: str
    model_name: Optional[str] = None
    fuel_type: Optional[str] = None
    dealer_name: Optional[str] = None
    tracking_status: Optional[str] = None
    hold_status: str = 'NONE'
    hold_reasons: List[str] = []
    current_station_name: Optional[str] = None
    current_station_seq: Optional[int] = None
    state_here: str  # NOT_ARRIVED / AT_STATION / COMPLETED
    arrived_at: Optional[datetime] = None
    elapsed_minutes: Optional[int] = None
    cycle_time_min: Optional[int] = None
    next_action: Optional[str] = None  # CHECK_IN / COMPLETE / None
    blocked_reason: Optional[str] = None  # why it can't be checked in here yet
    handoff_status: Optional[str] = None  # hand-over from this station: AWAITING_QA / QA_PASSED / RELEASED / RECEIVED
    next_station_name: Optional[str] = None
    can_release: bool = False  # QA passed everything mandatory; QA may pass it to the next station
    age_days: Optional[int] = None
    parts: List[PartRow]
    checksheets: List[ChecksheetBrief]
    qa_status: str = 'NOT_STARTED'


class ReleaseIn(BaseModel):
    remarks: Optional[str] = None


class ReleaseOut(BaseModel):
    message: str
    next_station_name: str


def _brief(st: StationMaster) -> StationBrief:
    return StationBrief(station_id=st.station_id, station_code=st.station_code, station_name=st.station_name,
                        sequence_no=st.sequence_no, requires_qa=bool(st.requires_qa),
                        cycle_time_min=st.standard_cycle_time_min)


async def _qa_status(db: AsyncSession, station_id: UUID, chassis_ids: List[UUID]) -> dict:
    if not chassis_ids:
        return {}
    rows = (await db.execute(
        select(QaChecksheetInstance.chassis_id, QaChecksheetInstance.station_qa_status, QaChecksheetInstance.overall_result)
        .where(QaChecksheetInstance.station_id == station_id, QaChecksheetInstance.chassis_id.in_(chassis_ids))
    )).all()
    out = {}
    for cid, sqa, result in rows:
        cur = out.get(cid, [])
        cur.append((result or 'PENDING', sqa or 'PENDING'))
        out[cid] = cur
    status = {}
    for cid, items in out.items():
        results = [r for r, _ in items]
        if any(r == 'FAILED' for r in results):
            status[cid] = 'FAILED'
        elif all(r == 'PASSED' for r in results):
            status[cid] = 'PASSED'
        else:
            status[cid] = 'IN_PROGRESS'
    return status


async def _checksheets(db: AsyncSession, station_id: UUID) -> List[ChecksheetBrief]:
    rows = (await db.execute(
        select(QaStationTemplateMap, QaChecksheetTemplate)
        .join(QaChecksheetTemplate, QaChecksheetTemplate.template_id == QaStationTemplateMap.template_id)
        .where(QaStationTemplateMap.station_id == station_id, QaChecksheetTemplate.is_active == True)  # noqa: E712
    )).all()
    seen, out = set(), []
    for m, t in rows:
        if t.template_id in seen:
            continue
        seen.add(t.template_id)
        out.append(ChecksheetBrief(template_id=t.template_id, template_code=t.template_code,
                                   template_name=t.template_name, is_mandatory=bool(m.is_mandatory)))
    return out


# ── Assignments (admin) ─────────────────────────────────────────────────────────
@router.get('/users', response_model=List[UserBrief])
async def list_users(db: AsyncSession = Depends(get_db), _=Depends(require_permission('LOCATIONS', 'view'))):
    rows = (await db.execute(select(LoginUsers).where(LoginUsers.is_active == True).order_by(LoginUsers.name))).scalars().all()  # noqa: E712
    return [UserBrief(user_id=u.user_id, name=u.name, employee_code=u.employee_code, role_code=u.role_code) for u in rows]


@router.get('/assignments', response_model=List[AssignmentOut])
async def list_assignments(
    station_id: Optional[UUID] = None,
    db: AsyncSession = Depends(get_db),
    _=Depends(require_permission('LOCATIONS', 'view')),
):
    stmt = (select(StationUserAssignment, LoginUsers, StationMaster)
            .join(LoginUsers, LoginUsers.user_id == StationUserAssignment.user_id)
            .join(StationMaster, StationMaster.station_id == StationUserAssignment.station_id))
    if station_id:
        stmt = stmt.where(StationUserAssignment.station_id == station_id)
    rows = (await db.execute(stmt.order_by(StationMaster.sequence_no, StationUserAssignment.role, LoginUsers.name))).all()
    return [AssignmentOut(assignment_id=a.assignment_id, user_id=u.user_id, user_name=u.name, employee_code=u.employee_code,
                          user_role_code=u.role_code, station_id=s.station_id, station_name=s.station_name,
                          station_code=s.station_code, sequence_no=s.sequence_no, role=a.role, is_active=a.is_active)
            for a, u, s in rows]


@router.post('/assignments', response_model=AssignmentOut, status_code=201)
async def create_assignment(
    payload: AssignmentCreate,
    db: AsyncSession = Depends(get_db),
    admin: LoginUsers = Depends(require_permission('LOCATIONS', 'edit')),
):
    if admin.role_code == 'QA_SUPERVISOR' and payload.role != 'QA':
        raise HTTPException(403, 'QA Supervisors can only assign QA personnel')
    user = await db.get(LoginUsers, payload.user_id)
    station = await db.get(StationMaster, payload.station_id)
    if not user or not station:
        raise HTTPException(404, 'User or station not found')
    existing = (await db.execute(select(StationUserAssignment).where(
        StationUserAssignment.user_id == payload.user_id,
        StationUserAssignment.station_id == payload.station_id,
        StationUserAssignment.role == payload.role,
    ))).scalars().first()
    if existing:
        if existing.is_active:
            raise HTTPException(409, f'{user.name} is already {payload.role} at {station.station_name}')
        existing.is_active = True
        row = existing
    else:
        row = StationUserAssignment(user_id=payload.user_id, station_id=payload.station_id,
                                    role=payload.role, created_by=admin.user_id)
        db.add(row)
    try:
        await db.commit()
    except IntegrityError:
        await db.rollback()
        raise HTTPException(409, 'This assignment already exists')
    await db.refresh(row)
    return AssignmentOut(assignment_id=row.assignment_id, user_id=user.user_id, user_name=user.name,
                         employee_code=user.employee_code, user_role_code=user.role_code,
                         station_id=station.station_id, station_name=station.station_name,
                         station_code=station.station_code, sequence_no=station.sequence_no,
                         role=row.role, is_active=row.is_active)


@router.delete('/assignments/{assignment_id}', status_code=204)
async def delete_assignment(
    assignment_id: UUID,
    db: AsyncSession = Depends(get_db),
    admin: LoginUsers = Depends(require_permission('LOCATIONS', 'edit')),
):
    row = await db.get(StationUserAssignment, assignment_id)
    if not row:
        raise HTTPException(404, 'Assignment not found')
    if admin.role_code == 'QA_SUPERVISOR' and row.role != 'QA':
        raise HTTPException(403, 'QA Supervisors can only manage QA assignments')
    await db.delete(row)
    await db.commit()


# ── Operator / QA workspace ─────────────────────────────────────────────────────
@router.get('/my-stations', response_model=MyStationsOut)
async def my_stations(
    role: str = Query('OPERATOR', pattern='^(OPERATOR|QA)$'),
    db: AsyncSession = Depends(get_db),
    user: LoginUsers = Depends(get_current_user),
):
    if user.role_code == 'ADMIN' or (user.role_code == 'QA_SUPERVISOR' and role == 'QA'):
        stmt = select(StationMaster).where(StationMaster.is_active == True)  # noqa: E712
        if role == 'QA':
            stmt = stmt.where(StationMaster.requires_qa == True)  # noqa: E712
        stations = (await db.execute(stmt.order_by(StationMaster.sequence_no))).scalars().all()
        return MyStationsOut(role=role, supervisor_mode=True, stations=[_brief(s) for s in stations])

    assigned = await assigned_station_ids(db, user.user_id, role)
    stmt = select(StationMaster).where(StationMaster.is_active == True)  # noqa: E712
    if assigned:
        stmt = stmt.where(StationMaster.station_id.in_(assigned))
    elif not await user_has_permission(db, user, ROLE_MODULE[role], 'view'):
        return MyStationsOut(role=role, supervisor_mode=False, stations=[])
    if role == 'QA' and not assigned:
        stmt = stmt.where(StationMaster.requires_qa == True)  # noqa: E712
    stations = (await db.execute(stmt.order_by(StationMaster.sequence_no))).scalars().all()
    return MyStationsOut(role=role, supervisor_mode=not assigned, stations=[_brief(s) for s in stations])


@router.get('/stations/{station_id}/board', response_model=StationBoardOut)
async def station_board(
    station_id: UUID,
    role: str = Query('OPERATOR', pattern='^(OPERATOR|QA)$'),
    db: AsyncSession = Depends(get_db),
    user: LoginUsers = Depends(get_current_user),
):
    await ensure_station_access(db, user, station_id, role)
    station = await db.get(StationMaster, station_id)
    if not station:
        raise HTTPException(404, 'Station not found')

    def chassis_row(cm, model_name, since, handoff=None, next_name=None):
        return BoardChassis(chassis_id=cm.chassis_id, chassis_no=cm.chassis_no, model_name=model_name,
                            hold_status=cm.production_hold_status or 'NONE', since=since,
                            elapsed_minutes=_elapsed(since), age_days=_age_days(cm),
                            handoff_status=handoff.status if handoff else None, next_station_name=next_name)

    base = (select(ChassisStationLog, ChassisMaster, VehicleModelMaster.model_number)
            .join(ChassisMaster, ChassisMaster.chassis_id == ChassisStationLog.chassis_id)
            .outerjoin(VehicleModelMaster, VehicleModelMaster.model_id == ChassisMaster.model_id))
    hbase = (select(ChassisStationHandoff, ChassisMaster, VehicleModelMaster.model_number)
             .join(ChassisMaster, ChassisMaster.chassis_id == ChassisStationHandoff.chassis_id)
             .outerjoin(VehicleModelMaster, VehicleModelMaster.model_id == ChassisMaster.model_id))
    names = {s.station_id: s.station_name for s in (await db.execute(select(StationMaster))).scalars().all()}

    # At this station now
    at_rows = (await db.execute(base.where(
        ChassisStationLog.station_id == station_id,
        ChassisStationLog.event_type == 'ARRIVAL',
        ChassisStationLog.status == 'AT_STATION',
    ).order_by(ChassisStationLog.scanned_at))).all()
    at_station = [chassis_row(cm, mn, log.scanned_at) for log, cm, mn in at_rows]
    seen = {c.chassis_id for c in at_station}

    # Work done here, waiting for QA (or passed by QA but not yet released)
    awaiting_qa = []
    for h, cm, mn in (await db.execute(hbase.where(
        ChassisStationHandoff.from_station_id == station_id,
        ChassisStationHandoff.status.in_(('AWAITING_QA', 'QA_PASSED')),
    ).order_by(ChassisStationHandoff.completed_at))).all():
        if cm.chassis_id not in seen:
            seen.add(cm.chassis_id)
            awaiting_qa.append(chassis_row(cm, mn, h.completed_at, h, names.get(h.to_station_id, 'end of line')))

    # Coming next: released to this station and not yet checked in
    queue = []
    for h, cm, mn in (await db.execute(hbase.where(
        ChassisStationHandoff.to_station_id == station_id,
        ChassisStationHandoff.status == 'RELEASED',
    ).order_by(ChassisStationHandoff.released_at))).all():
        if cm.chassis_id not in seen:
            seen.add(cm.chassis_id)
            queue.append(chassis_row(cm, mn, h.released_at, h))

    # Passed on from here recently
    completed_here = []
    for h, cm, mn in (await db.execute(hbase.where(
        ChassisStationHandoff.from_station_id == station_id,
        ChassisStationHandoff.status.in_(('RELEASED', 'RECEIVED')),
        ChassisStationHandoff.completed_at >= _now() - timedelta(days=7),
    ).order_by(desc(ChassisStationHandoff.completed_at)))).all():
        if cm.chassis_id not in seen:
            seen.add(cm.chassis_id)
            completed_here.append(chassis_row(cm, mn, h.released_at or h.completed_at, h, names.get(h.to_station_id, 'end of line')))

    # Chassis already on the floor before hand-overs existed (no hand-over history):
    # keep showing them using the station log, as before.
    with_handoffs = set((await db.execute(select(ChassisStationHandoff.chassis_id).distinct())).scalars().all())
    for log, cm, mn in (await db.execute(base.where(
        ChassisStationLog.station_id == station_id,
        ChassisStationLog.event_type == 'COMPLETION',
        ChassisStationLog.scanned_at >= _now() - timedelta(days=7),
        ChassisMaster.tracking_status == 'WIP',
    ).order_by(desc(ChassisStationLog.scanned_at)))).all():
        if cm.chassis_id not in seen and cm.chassis_id not in with_handoffs:
            seen.add(cm.chassis_id)
            completed_here.append(chassis_row(cm, mn, log.scanned_at))
    if station.sequence_no is not None:
        prev = (await db.execute(select(StationMaster).where(
            StationMaster.is_active == True, StationMaster.sequence_no < station.sequence_no  # noqa: E712
        ).order_by(desc(StationMaster.sequence_no)).limit(1))).scalars().first()
        wip = (await db.execute(
            select(ChassisMaster, VehicleModelMaster.model_number)
            .outerjoin(VehicleModelMaster, VehicleModelMaster.model_id == ChassisMaster.model_id)
            .where(ChassisMaster.tracking_status == 'WIP')
        )).all()
        for cm, mn in wip:
            if cm.chassis_id in seen or cm.chassis_id in with_handoffs:
                continue
            open_log = (await db.execute(select(ChassisStationLog.log_id).where(
                ChassisStationLog.chassis_id == cm.chassis_id, ChassisStationLog.status == 'AT_STATION'
            ))).first()
            if open_log:
                continue
            last = (await db.execute(select(ChassisStationLog).where(
                ChassisStationLog.chassis_id == cm.chassis_id, ChassisStationLog.event_type == 'COMPLETION'
            ).order_by(desc(ChassisStationLog.scanned_at)).limit(1))).scalars().first()
            if (prev is None and last is None) or (prev is not None and last is not None and last.station_id == prev.station_id):
                queue.append(chassis_row(cm, mn, last.scanned_at if last else cm.handed_to_production_at))

    everyone = at_station + queue + awaiting_qa + completed_here
    qa = await _qa_status(db, station_id, [c.chassis_id for c in everyone])
    for c in everyone:
        c.qa_status = qa.get(c.chassis_id, 'NOT_STARTED')

    parts = (await db.execute(select(RmMaster).where(
        RmMaster.station_id == station_id, RmMaster.is_active == True  # noqa: E712
    ).order_by(RmMaster.name).limit(200))).scalars().all()

    return StationBoardOut(
        station=_brief(station), description=station.station_description,
        at_station=at_station, queue=queue, awaiting_qa=awaiting_qa, completed_here=completed_here,
        checksheets=await _checksheets(db, station_id),
        parts=[PartRow(rm_id=p.rm_id, name=p.name, part_no=p.part_no, unit=p.unit_of_measurement) for p in parts],
    )


@router.get('/stations/{station_id}/chassis/{chassis_no}', response_model=ChassisAtStationOut)
async def chassis_at_station(
    station_id: UUID,
    chassis_no: str,
    role: str = Query('OPERATOR', pattern='^(OPERATOR|QA)$'),
    db: AsyncSession = Depends(get_db),
    user: LoginUsers = Depends(get_current_user),
):
    await ensure_station_access(db, user, station_id, role)
    station = await db.get(StationMaster, station_id)
    if not station:
        raise HTTPException(404, 'Station not found')
    cm = (await db.execute(select(ChassisMaster).where(
        func.lower(ChassisMaster.chassis_no) == chassis_no.strip().lower()
    ))).scalars().first()
    if not cm:
        raise HTTPException(404, f"Chassis '{chassis_no}' not found")

    model = await db.get(VehicleModelMaster, cm.model_id) if cm.model_id else None
    dealer = await db.get(CustomerMaster, cm.dealer_id) if cm.dealer_id else None
    current = await db.get(StationMaster, cm.current_station_id) if cm.current_station_id else None

    last_here = (await db.execute(select(ChassisStationLog).where(
        ChassisStationLog.chassis_id == cm.chassis_id, ChassisStationLog.station_id == station_id,
    ).order_by(desc(ChassisStationLog.scanned_at)).limit(1))).scalars().first()
    if last_here and last_here.status == 'AT_STATION' and last_here.event_type == 'ARRIVAL':
        state, arrived = 'AT_STATION', last_here.scanned_at
    elif last_here:
        state, arrived = 'COMPLETED', None
    else:
        state, arrived = 'NOT_ARRIVED', None

    on_hold = cm.production_hold_status == 'ON_HOLD'
    qa_here = (await _qa_status(db, station_id, [cm.chassis_id])).get(cm.chassis_id, 'NOT_STARTED')

    # Hand-over from this station (latest), if the operator has completed here
    from_here = (await db.execute(select(ChassisStationHandoff).where(
        ChassisStationHandoff.chassis_id == cm.chassis_id,
        ChassisStationHandoff.from_station_id == station_id,
        ChassisStationHandoff.status != 'SUPERSEDED',
    ).order_by(desc(ChassisStationHandoff.completed_at)).limit(1))).scalars().first()

    next_action, blocked_reason = None, None
    if not on_hold and cm.tracking_status in ('WIP', 'CHASSIS_IN'):
        if state == 'AT_STATION':
            next_action = 'COMPLETE'
        elif state == 'NOT_ARRIVED' or (from_here and from_here.status == 'AWAITING_QA' and qa_here == 'FAILED'):
            # Not here yet, or QA failed it here and it needs rework: check in if the hand-over allows.
            try:
                await flow.check_arrival(db, cm.chassis_id, station)
                next_action = 'CHECK_IN'
            except HTTPException as exc:
                blocked_reason = exc.detail

    holds = (await db.execute(select(ProductionHold.hold_reason).where(
        ProductionHold.chassis_id == cm.chassis_id, ProductionHold.status == 'ACTIVE'
    ))).scalars().all()

    snap = (await db.execute(
        select(ChassisBomSnapshot, RmMaster).join(RmMaster, RmMaster.rm_id == ChassisBomSnapshot.rm_id)
        .where(ChassisBomSnapshot.chassis_id == cm.chassis_id, ChassisBomSnapshot.station_id == station_id)
        .order_by(RmMaster.name)
    )).all()
    if snap:
        parts = [PartRow(rm_id=r.rm_id, name=r.name, part_no=r.part_no, unit=r.unit_of_measurement,
                         quantity=float(s.quantity or 0), consumed_qty=float(s.consumed_qty or 0)) for s, r in snap]
    else:
        rows = (await db.execute(select(RmMaster).where(
            RmMaster.station_id == station_id, RmMaster.is_active == True  # noqa: E712
        ).order_by(RmMaster.name).limit(200))).scalars().all()
        parts = [PartRow(rm_id=p.rm_id, name=p.name, part_no=p.part_no, unit=p.unit_of_measurement) for p in rows]

    return ChassisAtStationOut(
        chassis_id=cm.chassis_id, chassis_no=cm.chassis_no,
        model_name=model.model_number if model else None, fuel_type=cm.fuel_type,
        dealer_name=dealer.name if dealer else None, tracking_status=cm.tracking_status,
        hold_status=cm.production_hold_status or 'NONE', hold_reasons=list(holds),
        current_station_name=current.station_name if current else None,
        current_station_seq=current.sequence_no if current else None,
        state_here=state, arrived_at=arrived, elapsed_minutes=_elapsed(arrived),
        cycle_time_min=station.standard_cycle_time_min, next_action=next_action,
        blocked_reason=blocked_reason,
        handoff_status=from_here.status if from_here else None,
        next_station_name=await flow.station_name(db, from_here.to_station_id) if from_here else None,
        can_release=bool(from_here and from_here.status == 'QA_PASSED'),
        age_days=_age_days(cm),
        parts=parts, checksheets=await _checksheets(db, station_id),
        qa_status=qa_here,
    )


@router.post('/stations/{station_id}/chassis/{chassis_id}/release', response_model=ReleaseOut)
async def release_to_next_station(
    station_id: UUID,
    chassis_id: UUID,
    payload: ReleaseIn,
    db: AsyncSession = Depends(get_db),
    user: LoginUsers = Depends(get_current_user),
):
    """QA passes the chassis on: it now shows at the next station, which can check it in."""
    await ensure_station_access(db, user, station_id, 'QA')
    _, next_name = await flow.release(db, chassis_id, station_id, user.user_id, payload.remarks)
    await db.commit()
    return ReleaseOut(message=f"Passed to {next_name}", next_station_name=next_name)
