from unittest.mock import MagicMock, patch

from db.collection_landing_push import (
    TARGET_SOURCE_LISTING_PATH,
    TARGET_SOURCE_PROVISIONED,
    _localize_collection_row_skus,
    _push_one_magento,
    _push_one_shopify,
    _resolve_or_provision_target,
    provision_magento_category_target,
    provision_shopify_collection_target,
    push_collection_landing_channel,
    resolve_magento_category_target,
    resolve_shopify_collection_target,
    build_taxonomy_relationship_metadata,
)
from db.models import MasterTaxonomyChannelLink, MasterTaxonomyNode


COLLECTION_ROW = {
    "path_slug": "kitchen-cabinets/anna-stone-gray",
    "category_l1": "Kitchen Cabinets",
    "collection": "Anna Stone Gray",
    "title": "Anna Stone Gray",
    "short_description": "A beautiful collection",
}


def test_resolve_shopify_collection_target_from_listing_path(monkeypatch):
    session = MagicMock()

    with patch(
        "db.collection_landing_push._listing_path_target",
        return_value="gid://shopify/Collection/123",
    ):
        target = resolve_shopify_collection_target(session, COLLECTION_ROW, connection_id=10001)

    assert target["remote_id"] == "gid://shopify/Collection/123"
    assert target["source"] == TARGET_SOURCE_LISTING_PATH


def test_resolve_or_provision_skips_when_not_found_and_provision_disabled():
    session = MagicMock()
    with patch(
        "db.collection_landing_push.resolve_shopify_collection_target",
        return_value={"remote_id": None, "source": None, "remote_path": "anna-stone-gray"},
    ):
        target, skip = _resolve_or_provision_target(
            session,
            COLLECTION_ROW,
            channel_code="shopify",
            connection_id=10001,
            dry_run=False,
            provision_missing_remote=False,
            provision_fn=MagicMock(),
            missing_reason="shopify_collection_not_found",
        )

    assert target["remote_id"] is None
    assert skip == {
        "status": "skipped",
        "path_slug": COLLECTION_ROW["path_slug"],
        "reason": "shopify_collection_not_found",
        "target_source": None,
        "remote_path": "anna-stone-gray",
    }


def test_resolve_or_provision_dry_run_would_provision():
    session = MagicMock()
    with patch(
        "db.collection_landing_push.resolve_shopify_collection_target",
        return_value={"remote_id": None, "source": None, "remote_path": "anna-stone-gray"},
    ):
        target, skip = _resolve_or_provision_target(
            session,
            COLLECTION_ROW,
            channel_code="shopify",
            connection_id=10001,
            dry_run=True,
            provision_missing_remote=True,
            provision_fn=MagicMock(),
            missing_reason="shopify_collection_not_found",
        )

    assert target["remote_id"] is None
    assert skip["status"] == "preview"
    assert skip["would_provision"] is True
    assert skip["proposed_remote_title"] == "Anna Stone Gray"
    assert skip["proposed_remote_handle"] == "anna-stone-gray"


def test_localize_collection_row_skus_maps_sample_and_metadata():
    session = MagicMock()
    row = {
        "sample_product": {
            "master_sku": "ACH-SD",
            "sku": "ACH-SD",
            "channel_sku": "ACH-SD",
            "name": "Anna Caramel Harvest Sample Door",
        },
        "cta_rows": [{"action": "order_sample", "sku": "ACH-SD"}],
        "landing_metadata": {
            "sample_products": [{"expected_master_sku": "ACH-SD"}],
            "kitchen_packages": [{"package_code": "l_shape_8x10", "sku": "ACH-B12"}],
        },
    }

    with patch(
        "db.channel_sku_mapping.mapping_by_master",
        return_value={"ACH-SD": "HD-CA-SD", "ACH-B12": "HD-CA-B12"},
    ):
        localized = _localize_collection_row_skus(
            session,
            row,
            channel_code="shopify",
            connection_id=1000001,
        )

    assert localized["sample_product"]["master_sku"] == "ACH-SD"
    assert localized["sample_product"]["channel_sku"] == "HD-CA-SD"
    assert localized["sample_product"]["sku"] == "HD-CA-SD"
    assert localized["cta_rows"][0]["sku"] == "HD-CA-SD"
    sample_meta = localized["landing_metadata"]["sample_products"][0]
    assert sample_meta["expected_master_sku"] == "ACH-SD"
    assert sample_meta["channel_sku"] == "HD-CA-SD"
    package_meta = localized["landing_metadata"]["kitchen_packages"][0]
    assert package_meta["sku"] == "ACH-B12"
    assert package_meta["channel_sku"] == "HD-CA-B12"


def test_localize_collection_row_skus_leaves_brochure_suffixes_alone():
    session = MagicMock()
    row = {
        "landing_metadata": {
            "sample_products": [{"brochure_sku": "SD", "sample_sku_suffix": "SD"}],
        }
    }

    with patch("db.channel_sku_mapping.mapping_by_master", return_value={}):
        localized = _localize_collection_row_skus(
            session,
            row,
            channel_code="shopify",
            connection_id=1000001,
        )

    sample_meta = localized["landing_metadata"]["sample_products"][0]
    assert sample_meta["brochure_sku"] == "SD"
    assert sample_meta["sample_sku_suffix"] == "SD"
    assert "channel_sku" not in sample_meta


@patch("shopify.collection_landing_push.push_shopify_collection_landing", return_value={"fields": 3})
@patch("shopify.connections.build_client")
@patch("db.collection_landing_push._get_shopify_connection")
@patch("db.collection_landing_push.provision_shopify_collection_target")
@patch("db.collection_landing_push.resolve_shopify_collection_target")
def test_push_one_shopify_provisions_then_pushes(
    mock_resolve,
    mock_provision,
    mock_get_connection,
    mock_build_client,
    mock_push,
):
    mock_resolve.return_value = {"remote_id": None, "source": None, "remote_path": "anna-stone-gray"}
    mock_provision.return_value = {
        "remote_id": "gid://shopify/Collection/999",
        "source": TARGET_SOURCE_PROVISIONED,
        "remote_path": "anna-stone-gray",
        "provisioned": True,
    }
    mock_get_connection.return_value = MagicMock(shop_code="test-shop")
    mock_build_client.return_value = MagicMock()
    session = MagicMock()

    result = _push_one_shopify(
        session,
        COLLECTION_ROW,
        connection_id=10001,
        dry_run=False,
        provision_missing_remote=True,
    )

    mock_provision.assert_called_once()
    mock_push.assert_called_once()
    assert result["status"] == "pushed"
    assert result["remote_id"] == "gid://shopify/Collection/999"
    assert result["target_source"] == TARGET_SOURCE_PROVISIONED
    assert result["provisioned"] is True


@patch("magento.collection_landing_push.sync_magento_collection_products", return_value={"assigned": 3})
@patch("magento.collection_landing_push.push_magento_collection_landing", return_value={"fields": 4})
@patch("db.collection_landing_pages.apply_collection_to_skus", return_value={"upserted": 2})
@patch("db.collection_landing_push._build_magento_api")
@patch("db.collection_landing_push.provision_magento_category_target")
@patch("db.collection_landing_push.resolve_magento_category_target")
def test_push_one_magento_provisions_then_pushes(
    mock_resolve,
    mock_provision,
    mock_build_api,
    mock_apply_listing_paths,
    mock_push,
    mock_sync_products,
):
    mock_resolve.return_value = {"remote_id": None, "source": None, "remote_path": "Kitchen Cabinets/Anna Stone Gray"}
    mock_provision.return_value = {
        "remote_id": "42",
        "source": TARGET_SOURCE_PROVISIONED,
        "remote_path": "Kitchen Cabinets/Anna Stone Gray",
        "provisioned": True,
    }
    mock_build_api.return_value = (MagicMock(), 1)
    session = MagicMock()

    result = _push_one_magento(
        session,
        COLLECTION_ROW,
        connection_id=1,
        dry_run=False,
        provision_missing_remote=True,
    )

    mock_provision.assert_called_once()
    mock_push.assert_called_once()
    assert mock_push.call_args.args[0] == mock_build_api.return_value[0]
    pushed_kwargs = mock_push.call_args.kwargs
    assert pushed_kwargs["category_id"] == 42
    assert pushed_kwargs["collection_row"]["path_slug"] == COLLECTION_ROW["path_slug"]
    assert "is_collection" in pushed_kwargs["collection_row"]
    assert pushed_kwargs["dry_run"] is False
    assert result["status"] == "pushed"
    assert result["remote_id"] == "42"
    assert result["provisioned"] is True
    assert result["product_sync"] == {"assigned": 3}
    assert result["listing_apply"] == {"upserted": 2}


@patch("db.collection_landing_push._push_one_shopify")
@patch("db.collection_landing_push.get_collection_landing_page")
def test_push_collection_landing_channel_passes_provision_flag(mock_get_page, mock_push_one):
    mock_get_page.return_value = COLLECTION_ROW
    mock_push_one.return_value = {"status": "pushed", "path_slug": COLLECTION_ROW["path_slug"]}
    session = MagicMock()

    push_collection_landing_channel(
        session,
        channel_code="shopify",
        connection_id=10001,
        path_slug=COLLECTION_ROW["path_slug"],
        dry_run=False,
        provision_missing_remote=True,
    )

    mock_push_one.assert_called_once()
    assert mock_push_one.call_args.kwargs["provision_missing_remote"] is True


@patch("db.collection_landing_push._push_one_magento")
@patch("db.collection_landing_push.get_collection_landing_page")
def test_push_collection_landing_channel_passes_skip_collection_assets(mock_get_page, mock_push_one):
    mock_get_page.return_value = COLLECTION_ROW
    mock_push_one.return_value = {"status": "pushed", "path_slug": COLLECTION_ROW["path_slug"]}
    session = MagicMock()

    push_collection_landing_channel(
        session,
        channel_code="magento",
        connection_id=1,
        path_slug=COLLECTION_ROW["path_slug"],
        dry_run=False,
        skip_collection_assets=True,
    )

    mock_push_one.assert_called_once()
    assert mock_push_one.call_args.kwargs["skip_collection_assets"] is True


@patch("magento.collection_landing_push.sync_magento_collection_products", return_value={"assigned": 3})
@patch("magento.collection_landing_push.push_magento_collection_landing", return_value={"fields": 4})
@patch("db.collection_landing_pages.apply_collection_to_skus", return_value={"upserted": 2})
@patch("db.collection_landing_push._build_magento_api")
@patch("db.collection_landing_push.resolve_magento_category_target")
def test_push_one_magento_skip_collection_assets_uses_non_asset_field_keys(
    mock_resolve,
    mock_build_api,
    _mock_apply_listing_paths,
    mock_push,
    _mock_sync_products,
):
    mock_resolve.return_value = {"remote_id": "42", "source": None, "remote_path": "Kitchen Cabinets/Anna Stone Gray"}
    api = MagicMock()
    api.get_category.return_value = (200, {})
    mock_build_api.return_value = (api, 1)
    session = MagicMock()

    result = _push_one_magento(
        session,
        COLLECTION_ROW,
        connection_id=1,
        dry_run=False,
        provision_missing_remote=False,
        skip_collection_assets=True,
    )

    pushed_kwargs = mock_push.call_args.kwargs
    assert "pdf_url" not in pushed_kwargs["field_keys"]
    assert "hero_images" not in pushed_kwargs["field_keys"]
    assert "category_icon_url" not in pushed_kwargs["field_keys"]
    assert result["shopping_icons"]["skipped"] is True


@patch("db.collection_landing_push.upsert_listing_path_target")
@patch("shopify.collection_sync.create_shopify_collection")
@patch("shopify.connections.build_client")
@patch("db.collection_landing_push._get_shopify_connection")
def test_provision_shopify_collection_target_links_listing_path(
    mock_get_connection,
    mock_build_client,
    mock_create_collection,
    mock_upsert_target,
):
    mock_get_connection.return_value = MagicMock(shop_code="test-shop")
    mock_build_client.return_value = MagicMock()
    mock_create_collection.return_value = {
        "collection_id": "gid://shopify/Collection/555",
        "handle": "anna-stone-gray",
        "title": "Anna Stone Gray",
    }
    session = MagicMock()

    with patch("db.collection_landing_push.upsert_listing_path") as mock_upsert_path:
        result = provision_shopify_collection_target(
            session,
            COLLECTION_ROW,
            connection_id=10001,
        )

    mock_upsert_path.assert_called_once()
    mock_upsert_target.assert_called_once()
    assert mock_upsert_target.call_args.kwargs["remote_id"] == "gid://shopify/Collection/555"
    assert mock_upsert_target.call_args.kwargs["taxonomy_kind"] == "collection"
    assert result["source"] == TARGET_SOURCE_PROVISIONED


@patch("db.collection_landing_push._can_provision_magento_category_path", return_value=True)
@patch("db.collection_landing_push._authoritative_magento_collection_row", return_value=COLLECTION_ROW)
@patch("db.collection_landing_push.upsert_listing_path_target")
@patch("db.channel_catalog_provision._ensure_magento_category_path", return_value=77)
@patch("db.collection_landing_push._build_magento_api")
def test_provision_magento_category_target_links_listing_path(
    mock_build_api,
    mock_ensure_path,
    mock_upsert_target,
    _mock_authoritative,
    _mock_can_provision,
):
    mock_build_api.return_value = (MagicMock(), 1)
    session = MagicMock()

    with patch("db.collection_landing_push.upsert_listing_path") as mock_upsert_path:
        result = provision_magento_category_target(
            session,
            COLLECTION_ROW,
            connection_id=1,
        )

    mock_upsert_path.assert_called_once()
    mock_ensure_path.assert_called_once()
    mock_upsert_target.assert_called_once()
    assert mock_upsert_target.call_args.kwargs["remote_id"] == "77"
    assert mock_upsert_target.call_args.kwargs["taxonomy_kind"] == "category"
    assert result["remote_id"] == "77"
    assert result["source"] == TARGET_SOURCE_PROVISIONED


def test_describe_collection_channel_target_linked():
    session = MagicMock()
    with patch(
        "db.collection_landing_push.resolve_shopify_collection_target",
        return_value={
            "remote_id": "gid://shopify/Collection/1",
            "source": TARGET_SOURCE_LISTING_PATH,
            "remote_path": "anna-stone-gray",
        },
    ):
        from db.collection_landing_push import READINESS_LINKED, describe_collection_channel_target

        result = describe_collection_channel_target(
            session,
            COLLECTION_ROW,
            channel_code="shopify",
            connection_id=10001,
        )

    assert result["readiness"] == READINESS_LINKED
    assert result["linked"] is True
    assert result["can_provision"] is False
    assert result["skip_reason"] is None


def test_describe_collection_channel_target_provisionable():
    session = MagicMock()
    with patch(
        "db.collection_landing_push.resolve_shopify_collection_target",
        return_value={"remote_id": None, "source": None, "remote_path": "anna-stone-gray"},
    ):
        with patch("db.collection_landing_push._get_shopify_connection", return_value=MagicMock()):
            from db.collection_landing_push import (
                READINESS_PROVISIONABLE,
                SKIP_REASON_SHOPIFY_NOT_FOUND,
                describe_collection_channel_target,
            )

            result = describe_collection_channel_target(
                session,
                COLLECTION_ROW,
                channel_code="shopify",
                connection_id=10001,
            )

    assert result["readiness"] == READINESS_PROVISIONABLE
    assert result["can_provision"] is True
    assert result["skip_reason"] == SKIP_REASON_SHOPIFY_NOT_FOUND


def test_relationship_metadata_flattens_parent_children_and_siblings(catalog_intent_session):
    session = catalog_intent_session
    kitchen = MasterTaxonomyNode(
        node_kind="hub", code="kitchen", name="Kitchen Cabinets", path_slug="kitchen-cabinets"
    )
    session.add(kitchen)
    session.flush()
    base = MasterTaxonomyNode(
        parent_id=kitchen.id,
        node_kind="category",
        code="base",
        name="Base Cabinets",
        path_slug="kitchen-cabinets/base-cabinets",
    )
    anna = MasterTaxonomyNode(
        parent_id=kitchen.id,
        node_kind="collection",
        code="anna-stone-gray",
        name="Anna Stone Gray",
        path_slug="kitchen-cabinets/anna-stone-gray",
    )
    snow = MasterTaxonomyNode(
        parent_id=kitchen.id,
        node_kind="collection",
        code="anna-snow-white",
        name="Anna Snow White",
        path_slug="kitchen-cabinets/anna-snow-white",
    )
    session.add_all([base, anna, snow])
    session.flush()
    for node, remote_id in (
        (kitchen, "gid://shopify/Collection/1"),
        (base, "gid://shopify/Collection/2"),
        (anna, "gid://shopify/Collection/3"),
        (snow, "gid://shopify/Collection/4"),
    ):
        session.add(
            MasterTaxonomyChannelLink(
                taxonomy_node_id=node.id,
                channel_code="shopify",
                connection_id=1,
                taxonomy_kind="collection",
                remote_id=remote_id,
                is_active=True,
            )
        )
    session.flush()

    anna_metadata = build_taxonomy_relationship_metadata(
        session,
        path_slug=anna.path_slug,
        channel_code="shopify",
        connection_id=1000001,
    )
    kitchen_metadata = build_taxonomy_relationship_metadata(
        session,
        path_slug=kitchen.path_slug,
        channel_code="shopify",
        connection_id=1000001,
    )

    assert anna_metadata["is_collection"] is True
    assert anna_metadata["parent_remote_id"] == "gid://shopify/Collection/1"
    assert set(anna_metadata["sibling_remote_ids"]) == {
        "gid://shopify/Collection/2",
        "gid://shopify/Collection/4",
    }
    assert kitchen_metadata["has_collections"] is True
    assert set(kitchen_metadata["child_remote_ids"]) == {
        "gid://shopify/Collection/2",
        "gid://shopify/Collection/3",
        "gid://shopify/Collection/4",
    }


@patch("db.collection_landing_push._build_magento_api")
def test_confirm_magento_category_target_clears_missing_remote_id(mock_build_api):
    from db.collection_landing_push import (
        TARGET_SOURCE_LISTING_PATH,
        _confirm_magento_category_target,
    )

    api = MagicMock()
    api.get_category.return_value = (404, None)
    mock_build_api.return_value = (api, 2)
    session = MagicMock()

    with patch(
        "db.collection_landing_push._invalidate_stale_magento_category_mappings",
    ) as invalidate, patch(
        "db.master_taxonomy_merge.resolve_canonical_path_slug",
        return_value="kitchen-cabinets/eleanor-ocean-blue",
    ):
        result = _confirm_magento_category_target(
            session,
            {
                "remote_id": "90",
                "source": TARGET_SOURCE_LISTING_PATH,
                "remote_path": "Kitchen Cabinets/Ocean Blue",
            },
            path_slug="kitchen-cabinets/eleanor-ocean-blue",
            connection_id=2,
        )

    assert result["remote_id"] is None
    invalidate.assert_called_once_with(
        session,
        path_slug="kitchen-cabinets/eleanor-ocean-blue",
        connection_id=2,
        remote_id="90",
    )


@patch("db.collection_landing_push._authoritative_magento_collection_row", return_value=COLLECTION_ROW)
@patch("db.collection_landing_push._confirm_magento_category_target")
@patch("db.collection_landing_push.resolve_magento_category_target")
def test_resolve_or_provision_reprovisions_stale_magento_target(
    mock_resolve, mock_confirm, _mock_authoritative
):
    from db.collection_landing_push import (
        TARGET_SOURCE_LISTING_PATH,
        TARGET_SOURCE_PROVISIONED,
        _resolve_or_provision_target,
    )

    mock_resolve.return_value = {
        "remote_id": "90",
        "source": TARGET_SOURCE_LISTING_PATH,
        "remote_path": "Kitchen Cabinets/Ocean Blue",
    }
    mock_confirm.return_value = {
        "remote_id": None,
        "source": None,
        "remote_path": "Kitchen Cabinets/Ocean Blue",
    }
    provision_fn = MagicMock()
    provision_fn.return_value = {
        "remote_id": "120",
        "source": TARGET_SOURCE_PROVISIONED,
        "remote_path": "Kitchen Cabinets/Ocean Blue",
        "provisioned": True,
    }
    session = MagicMock()

    target, skip = _resolve_or_provision_target(
        session,
        COLLECTION_ROW,
        channel_code="magento",
        connection_id=2,
        dry_run=False,
        provision_missing_remote=True,
        provision_fn=provision_fn,
        missing_reason="magento_category_not_found",
    )

    mock_confirm.assert_called_once()
    provision_fn.assert_called_once()
    assert skip is None
    assert target["remote_id"] == "120"
    assert target["provisioned"] is True
