from operator import or_


def workflow_by_status(
    session, model, model_id_attr, workflow_status=None, status=None, finalized=None
):
    """
    Fetches all allele_id/analysis_id where the last interpretation matches provided
    workflow status and/or status.

    :param model: AlleleInterpretation or AnalysisInterpretation
    :param model_id_attr: 'allele_id' or 'analysis_id'

    Query resembles something like this:
     SELECT s.id FROM (select DISTINCT ON (analysis_id) id, workflow_status, status
     from analysisinterpretation order by analysis_id, date_last_update desc) AS
     s where s.workflow_status = :status;

    Using DISTINCT ON and ORDER BY will select one row, giving us the latest interpretation workflow status.
    See https://www.postgresql.org/docs/10.0/static/sql-select.html#SQL-DISTINCT
    """

    if workflow_status is None and status is None and finalized is None:
        raise RuntimeError(
            "You must provide either 'workflow_status', 'status' or 'finalized' argument"
        )

    assert model_id_attr in ["allele_id", "analysis_id"]

    latest_interpretation = (
        session.query(
            getattr(model, model_id_attr), model.workflow_status, model.status, model.finalized
        )
        .order_by(getattr(model, model_id_attr), model.date_created.desc())
        .distinct(getattr(model, model_id_attr))  # DISTINCT ON
        .subquery()
    )

    filters = []
    if workflow_status:
        filters.append(latest_interpretation.c.workflow_status == workflow_status)
    if status:
        filters.append(latest_interpretation.c.status == status)
    if finalized is not None:
        if finalized:
            filters.append(latest_interpretation.c.finalized.is_(True))
        else:
            filters.append(
                or_(
                    latest_interpretation.c.finalized.is_(None),
                    latest_interpretation.c.finalized.is_(False),
                )
            )
    return session.query(
        getattr(latest_interpretation.c, model_id_attr).label(model_id_attr)
    ).filter(*filters)