"""Celery task: ночной cross-load ETL tradein.houses → gendesign.newbuilding_listings.

Расписание: 03:30 МСК (hardcoded в beat_schedule, как cbr/poi/supply-layers).
Идемпотентен: UPSERT ON CONFLICT (source, ext_house_id).
Если TRADEIN_DATABASE_URL не задан → warn-log, возвращает {"disabled": True}.

Breadcrumb (issue #2445 D6): прогон логируется в objective_scrape_runs (тот же
журнал, что используют соседние nightly/weekly-таски — см.
app/workers/tasks/objective_etl.py) с group_name='newbuilding-crossload', чтобы
сбой был виден оператору в admin-таблице логов, а не только в Celery FAILURE +
GlitchTip. Таблица переиспользуется как есть — без новой миграции; поля
rows_lots/rows_history/requests_count не про Objective-домен здесь, а про
source_rows/upserted/skipped cross-load'а (см. комментарии у _finish_run).
"""

from __future__ import annotations

import logging
from typing import Any

from sqlalchemy import text
from sqlalchemy.orm import Session

from app.core.db import SessionLocal
from app.services.etl.newbuilding_crossload import run_crossload
from app.workers.celery_app import celery_app

logger = logging.getLogger(__name__)

_GROUP_NAME = "newbuilding-crossload"


def _start_run(db: Session, triggered_by: str) -> int:
    """Создать breadcrumb-строку в objective_scrape_runs, статус 'running'."""
    row = db.execute(
        text(
            """
            INSERT INTO objective_scrape_runs (group_name, triggered_by, status)
            VALUES (:gn, :tb, 'running')
            RETURNING run_id
            """
        ),
        {"gn": _GROUP_NAME, "tb": triggered_by},
    ).scalar_one()
    db.commit()
    return int(row)


def _finish_run(
    db: Session, run_id: int, *, status: str, error: str | None = None, **counts: int
) -> None:
    """Обновить breadcrumb-строку финальным статусом ('done' | 'failed') + счётчиками."""
    sets = ["finished_at = NOW()", "status = :status", "error = :error"]
    params: dict[str, Any] = {"rid": run_id, "status": status, "error": error}
    for k, v in counts.items():
        sets.append(f"{k} = :{k}")
        params[k] = v
    db.execute(
        text(f"UPDATE objective_scrape_runs SET {', '.join(sets)} WHERE run_id = :rid"),
        params,
    )
    db.commit()


@celery_app.task(name="tasks.etl_newbuilding_crossload.etl_newbuilding_crossload")
def etl_newbuilding_crossload(triggered_by: str = "beat") -> dict:
    """Cross-load tradein.houses → gendesign.newbuilding_listings (#976).

    Вызывается beat'ом в 03:30 МСК и вручную через POST /api/v1/admin/scrape/newbuilding-crossload.

    Args:
        triggered_by: 'beat' | 'manual' | 'api' — для журнала objective_scrape_runs.

    Breadcrumb-строка в objective_scrape_runs логирует start/success/failure —
    на исключении из run_crossload() помечает run 'failed' и ре-рейзит, чтобы
    Celery ушёл в FAILURE и GlitchTip продолжил ловить сбой (issue #2445 D6).
    """
    db = SessionLocal()
    run_id: int | None = None
    try:
        run_id = _start_run(db, triggered_by)
        logger.info("task etl_newbuilding_crossload: start run_id=%s", run_id)

        result = run_crossload(db=db)

        if result.get("disabled"):
            _finish_run(db, run_id, status="done", error="TRADEIN_DATABASE_URL not set")
            logger.info("task etl_newbuilding_crossload: done (disabled) run_id=%s", run_id)
            return {"run_id": run_id, **result}

        _finish_run(
            db,
            run_id,
            status="done",
            error=None,
            rows_history=result.get("source_rows", 0),
            rows_lots=result.get("upserted", 0),
            requests_count=result.get("skipped", 0),
        )
        logger.info("task etl_newbuilding_crossload: done run_id=%s — %s", run_id, result)
        return {"run_id": run_id, **result}

    except Exception as e:
        logger.exception("task etl_newbuilding_crossload: failed run_id=%s: %s", run_id, e)
        if run_id is not None:
            try:
                _finish_run(db, run_id, status="failed", error=f"{type(e).__name__}: {e}")
            except Exception:
                # Breadcrumb-flush не должен скрыть исходное исключение — Celery
                # всё равно должен уйти в FAILURE + GlitchTip его поймает.
                logger.exception(
                    "task etl_newbuilding_crossload: failed to write failure breadcrumb "
                    "for run_id=%s",
                    run_id,
                )
        raise
    finally:
        db.close()
