from __future__ import annotations

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

from supply_infra.db.models.pipeline_run import PipelineRun
from supply_infra.db.models.pipeline_step_run import PipelineStepRun
from supply_infra.pipeline import run_service


def _session_factory() -> sessionmaker[Session]:
    engine = create_engine("sqlite+pysqlite:///:memory:")
    PipelineRun.__table__.create(engine)
    PipelineStepRun.__table__.create(engine)
    return sessionmaker(bind=engine, expire_on_commit=False)


def _run(status: str = "cancelled") -> PipelineRun:
    return PipelineRun(
        run_id="run-cancelled",
        dedupe_key="supply_pipeline:20260727:full",
        pipeline_key="supply_pipeline",
        biz_dt="20260727",
        trigger_type="api",
        run_mode="full",
        dry_run=True,
        status=status,
        current_step=None,
        deadline_at=None,
        config_snapshot_json={},
        date_snapshot_json={},
        summary_json={"old": "summary"},
        error_code="cancellation_requested",
        error_message="cancelled",
    )


def _step(
    *,
    step_run_id: str,
    key: str,
    order: int,
    status: str,
) -> PipelineStepRun:
    return PipelineStepRun(
        step_run_id=step_run_id,
        run_id="run-cancelled",
        step_key=key,
        step_order=order,
        attempt=1,
        status=status,
        critical=True,
        dependency_snapshot_json=[],
        input_snapshot_json={},
        timeout_seconds=60,
        max_attempts=1,
        retryable=False,
        error_code="cancelled" if status == "cancelled" else None,
        error_message="Cancellation requested" if status == "cancelled" else None,
    )


def _patch_session(monkeypatch, factory: sessionmaker[Session]) -> None:
    @contextmanager
    def get_test_session():
        session = factory()
        try:
            yield session
            session.commit()
        except Exception:
            session.rollback()
            raise
        finally:
            session.close()

    monkeypatch.setattr(run_service, "get_session", get_test_session)


def test_cancelled_run_resumes_from_first_cancelled_step(monkeypatch) -> None:
    factory = _session_factory()
    _patch_session(monkeypatch, factory)
    with factory() as session:
        session.add_all(
            [
                _run(),
                _step(
                    step_run_id="step-1",
                    key="global_tree_sync",
                    order=1,
                    status="succeeded",
                ),
                _step(
                    step_run_id="step-2",
                    key="source_video_sync",
                    order=2,
                    status="cancelled",
                ),
                _step(
                    step_run_id="step-3",
                    key="demand_grade",
                    order=3,
                    status="cancelled",
                ),
            ]
        )
        session.commit()

    assert run_service.resume_pipeline_run("run-cancelled") is True

    with factory() as session:
        run = session.get(PipelineRun, "run-cancelled")
        assert run is not None
        assert run.status == "queued"
        assert run.current_step == "source_video_sync"
        assert run.finished_at is None
        assert run.summary_json is None
        assert run.error_code is None
        assert run.error_message is None

        steps = (
            session.query(PipelineStepRun)
            .filter(PipelineStepRun.run_id == run.run_id)
            .order_by(PipelineStepRun.step_order, PipelineStepRun.attempt)
            .all()
        )
        assert [(step.step_key, step.attempt, step.status) for step in steps] == [
            ("global_tree_sync", 1, "succeeded"),
            ("source_video_sync", 1, "cancelled"),
            ("source_video_sync", 2, "ready"),
            ("demand_grade", 1, "pending"),
        ]


def test_cancelling_run_cannot_resume_before_cancellation_finishes(monkeypatch) -> None:
    factory = _session_factory()
    _patch_session(monkeypatch, factory)
    with factory() as session:
        session.add_all(
            [
                _run(status="cancelling"),
                _step(
                    step_run_id="step-1",
                    key="source_video_sync",
                    order=1,
                    status="running",
                ),
            ]
        )
        session.commit()

    assert run_service.resume_pipeline_run("run-cancelled") is False
