from datetime import datetime

from app.jobs.channel_jobs import summarize_result
from app.jobs.channel_scheduler import next_run_after


def test_enqueue_shopify_product_push_job_stores_compat_connection_in_options():
    from unittest.mock import MagicMock

    from app.jobs.channel_jobs import enqueue_shopify_product_push_job

    session = MagicMock()
    job = enqueue_shopify_product_push_job(
        session,
        channel_connection_id=1_000_001,
        native_connection_id=1,
        channel_code="my-shop",
        skus=["SKU-A", "SKU-B"],
        force=True,
        dry_run=False,
        shop_code="my-shop",
        notes="test",
    )
    session.add.assert_called_once_with(job)
    session.flush.assert_called_once()
    assert job.job_type == "push"
    assert job.channel_type == "shopify"
    assert job.mode == "full"
    assert job.result["_options"]["connection_id"] == 1_000_001
    assert job.result["_options"]["skus"] == ["SKU-A", "SKU-B"]
    assert job.result["_options"]["force"] is True


def test_enqueue_shopify_product_push_jobs_batched_splits_large_lists():
    from unittest.mock import MagicMock

    from app.jobs.channel_jobs import enqueue_shopify_product_push_jobs_batched

    session = MagicMock()
    skus = [f"SKU-{index}" for index in range(450)]
    jobs = enqueue_shopify_product_push_jobs_batched(
        session,
        channel_connection_id=1_000_001,
        native_connection_id=1,
        channel_code="my-shop",
        skus=skus,
        batch_size=200,
        shop_code="my-shop",
        notes="master_products_push",
    )
    assert len(jobs) == 3
    assert session.add.call_count == 3
    assert len(jobs[0].result["_options"]["skus"]) == 200
    assert len(jobs[1].result["_options"]["skus"]) == 200
    assert len(jobs[2].result["_options"]["skus"]) == 50
    assert jobs[1].notes == "master_products_push batch 2/3"


def test_enqueue_shopify_push_supersedes_older_queued_jobs(catalog_intent_session):
    from sqlalchemy import select

    from app.jobs.channel_jobs import enqueue_shopify_product_push_job
    from db.models import ChannelJob

    session = catalog_intent_session
    first = enqueue_shopify_product_push_job(
        session,
        channel_connection_id=1_000_001,
        native_connection_id=1,
        channel_code="my-shop",
        skus=["OLD-1"],
        dry_run=False,
        notes="first push",
    )
    second = enqueue_shopify_product_push_job(
        session,
        channel_connection_id=1_000_001,
        native_connection_id=1,
        channel_code="my-shop",
        skus=["NEW-1", "NEW-2"],
        dry_run=False,
        notes="second push",
    )
    session.flush()

    first_row = session.get(ChannelJob, first.id)
    second_row = session.get(ChannelJob, second.id)
    assert first_row.status == "cancelled"
    assert second_row.status == "queued"
    queued = session.scalars(
        select(ChannelJob).where(
            ChannelJob.channel_type == "shopify",
            ChannelJob.native_connection_id == 1,
            ChannelJob.job_type == "push",
            ChannelJob.status == "queued",
        )
    ).all()
    assert len(queued) == 1
    assert queued[0].id == second.id


def test_enqueue_shopify_push_batched_supersedes_once_before_batches(catalog_intent_session):
    from sqlalchemy import select

    from app.jobs.channel_jobs import enqueue_shopify_product_push_job, enqueue_shopify_product_push_jobs_batched
    from db.models import ChannelJob

    session = catalog_intent_session
    stale = enqueue_shopify_product_push_job(
        session,
        channel_connection_id=1_000_001,
        native_connection_id=1,
        channel_code="my-shop",
        skus=["STALE"],
        dry_run=False,
        notes="stale push",
        supersede_queued=False,
    )
    session.flush()

    jobs = enqueue_shopify_product_push_jobs_batched(
        session,
        channel_connection_id=1_000_001,
        native_connection_id=1,
        channel_code="my-shop",
        skus=[f"SKU-{index}" for index in range(450)],
        batch_size=200,
        notes="batched push",
    )
    session.flush()

    stale_row = session.get(ChannelJob, stale.id)
    assert stale_row.status == "cancelled"
    queued = list(
        session.scalars(
            select(ChannelJob).where(
                ChannelJob.channel_type == "shopify",
                ChannelJob.native_connection_id == 1,
                ChannelJob.job_type == "push",
                ChannelJob.status == "queued",
            )
        ).all()
    )
    assert len(queued) == len(jobs) == 3


def test_summarize_shopify_push_summary():
    # Shape returned by shopify.product_sync.push_products
    result = {
        "status": "ok",
        "dry_run": False,
        "total": 25,
        "pushed": 20,
        "skipped_unchanged": 5,
        "failed": 0,
    }
    outcome = summarize_result(result)

    assert outcome["status"] == "completed"
    assert outcome["total_count"] == 25
    assert outcome["success_count"] == 20
    assert outcome["error_count"] == 0


def test_summarize_failed_result_does_not_report_success_counts():
    result = {"status": "failed", "total": 10, "error": "boom"}
    outcome = summarize_result(result)

    assert outcome["status"] == "failed"
    assert outcome["success_count"] == 0
    assert outcome["error_count"] == 1


def test_summarize_disabled_kill_switch_maps_to_failed():
    result = {"status": "disabled", "detail": "No active Shopify connection (kill switch engaged)"}
    outcome = summarize_result(result)

    assert outcome["status"] == "failed"
    assert "kill switch" in outcome["notes"]


def test_summarize_delegated_and_not_implemented_pass_through():
    assert summarize_result({"status": "delegated"})["status"] == "delegated"
    assert summarize_result({"status": "not_implemented"})["status"] == "not_implemented"


def test_summarize_success_string_status_from_attribute_sync():
    result = {"status": "success", "counts": {"registry_rows": 42}}
    assert summarize_result(result)["status"] == "completed"


def test_next_run_after_step_and_daily():
    assert next_run_after("*/15 * * * *", datetime(2026, 6, 12, 10, 7)) == datetime(2026, 6, 12, 10, 15)
    # Strictly after "now": firing minute itself moves to the next day.
    assert next_run_after("0 11 * * *", datetime(2026, 6, 12, 11, 0)) == datetime(2026, 6, 13, 11, 0)


def test_next_run_after_weekday_and_ranges():
    # 2026-06-12 is a Friday; cron weekday 1 = Monday.
    assert next_run_after("30 8 * * 1", datetime(2026, 6, 12, 12, 0)) == datetime(2026, 6, 15, 8, 30)
    assert next_run_after("0 9-11 * * *", datetime(2026, 6, 12, 10, 30)) == datetime(2026, 6, 12, 11, 0)
