from __future__ import annotations

import pandas as pd
from sqlalchemy import select

from db.manual_attributes import (
    DEFAULT_MANUAL_ATTRIBUTES,
    ensure_default_manual_attributes,
    list_manual_attributes,
    locked_values_for_code,
    publish_manual_attributes,
    replace_manual_attributes,
    sync_manual_attribute_enabled_filters,
)
from db.master_catalog import import_master_sku_dataframe
from db.master_product_filters import list_master_attribute_values
from db.channel_listing_path import enabled_filters_for_path_slug, upsert_listing_path
from db.models import MasterAttributeDefinition, MasterCollectionRegistry, MasterProduct, MasterProductAttributeValue


def test_ensure_default_manual_attributes_seeds_assembled_or_rta(catalog_intent_session):
    session = catalog_intent_session
    result = ensure_default_manual_attributes(session)
    assert result["seeded_count"] == 1
    assert result["count"] == 1
    attr = result["attributes"][0]
    assert attr["attribute_code"] == "assembled_or_rta"
    assert attr["is_locked"] is True
    assert [item["value"] for item in attr["values"]] == ["Assembled", "Unassembled/RTA"]

    again = ensure_default_manual_attributes(session)
    assert again["seeded_count"] == 0
    assert again["count"] == 1


def test_replace_manual_attributes_upserts_values(catalog_intent_session):
    session = catalog_intent_session
    result = replace_manual_attributes(
        session,
        {
            "attributes": [
                {
                    "attribute_code": "color_family",
                    "label": "Color Family",
                    "is_locked": True,
                    "unknown_policy": "drop",
                    "values": [
                        {"value": "Brown", "aliases": ["brown", "espresso"]},
                        {"value": "White", "aliases": ["white", "pearl"]},
                    ],
                }
            ]
        },
    )
    assert result["saved_count"] == 1
    assert result["attributes"][0]["attribute_code"] == "color_family"
    assert locked_values_for_code(session, "color_family") == ["Brown", "White"]


def test_replace_manual_attributes_round_trips_scope_metadata(catalog_intent_session):
    session = catalog_intent_session
    result = replace_manual_attributes(
        session,
        {
            "attributes": [
                {
                    "attribute_code": "door_style",
                    "label": "Door Style",
                    "data_type": "select",
                    "purpose": "presentation",
                    "hub_path_slugs": ["bathroom-vanities", "interior-doors"],
                    "taxonomy_path_slugs": [
                        "bathroom-vanities/dayton",
                        "interior-doors/shaker",
                    ],
                    "collection_codes": ["DAY", "SHAKER"],
                    "publish_to_magento": True,
                    "is_locked": True,
                    "unknown_policy": "drop",
                    "values": [
                        {"value": "Shaker", "aliases": ["shaker"]},
                        {"value": "Slim", "aliases": ["slim"]},
                    ],
                }
            ]
        },
    )
    row = result["attributes"][0]
    assert row["data_type"] == "select"
    assert row["purpose"] == "presentation"
    assert row["hub_path_slugs"] == ["bathroom-vanities", "interior-doors"]
    assert row["taxonomy_path_slugs"] == ["bathroom-vanities/dayton", "interior-doors/shaker"]
    assert row["collection_codes"] == ["DAY", "SHAKER"]
    assert row["publish_to_magento"] is True


def test_publish_manual_attributes_upserts_master_attribute_definition(catalog_intent_session):
    session = catalog_intent_session
    replace_manual_attributes(
        session,
        {
            "attributes": [
                {
                    "attribute_code": "vanity_finish",
                    "label": "Vanity Finish",
                    "data_type": "select",
                    "purpose": "presentation",
                    "hub_path_slugs": ["bathroom-vanities"],
                    "taxonomy_path_slugs": ["bathroom-vanities/dayton"],
                    "collection_codes": ["DAY"],
                    "publish_to_magento": True,
                    "is_locked": True,
                    "unknown_policy": "drop",
                    "values": [
                        {"value": "White", "aliases": ["white"]},
                    ],
                }
            ]
        },
    )

    result = publish_manual_attributes(session)
    assert result["published_count"] == 1
    assert result["published_codes"] == ["vanity_finish"]

    definition = session.scalar(
        select(MasterAttributeDefinition).where(MasterAttributeDefinition.attribute_code == "vanity_finish")
    )
    assert definition is not None
    assert definition.data_type == "select"
    assert definition.purpose == "presentation"
    assert definition.target_scopes["manual_attribute"] is True
    assert definition.target_scopes["hub_path_slugs"] == ["bathroom-vanities"]
    assert definition.target_scopes["taxonomy_path_slugs"] == ["bathroom-vanities/dayton"]
    assert definition.target_scopes["collection_codes"] == ["DAY"]


def test_sync_manual_attribute_enabled_filters_inherits_hub_scope_to_descendants(catalog_intent_session):
    session = catalog_intent_session
    upsert_listing_path(
        session,
        path_slug="bathroom-vanities",
        title="Bathroom Vanities",
        path_kind="hub",
        enabled_filters=["collection_availability"],
    )
    upsert_listing_path(
        session,
        path_slug="bathroom-vanities/dayton",
        title="Dayton",
        path_kind="collection",
        parent_path_slug="bathroom-vanities",
    )
    session.add(
        MasterCollectionRegistry(
            code="DAY",
            name="Dayton",
            path_slug="bathroom-vanities/dayton",
            is_active=True,
        )
    )
    replace_manual_attributes(
        session,
        {
            "attributes": [
                {
                    "attribute_code": "door_style",
                    "label": "Door Style",
                    "data_type": "select",
                    "hub_path_slugs": ["bathroom-vanities"],
                    "values": [{"value": "Shaker"}],
                }
            ]
        },
    )

    result = sync_manual_attribute_enabled_filters(session)

    assert result["listing_paths_updated"] >= 2
    assert enabled_filters_for_path_slug(session, "bathroom-vanities") == ["collection_availability", "door_style"]
    assert enabled_filters_for_path_slug(session, "bathroom-vanities/dayton") == ["collection_availability", "door_style"]


def test_publish_manual_attributes_respects_disabled_channels_for_magento_filter_sync(catalog_intent_session):
    session = catalog_intent_session
    upsert_listing_path(
        session,
        path_slug="interior-doors",
        title="Interior Doors",
        path_kind="hub",
    )
    replace_manual_attributes(
        session,
        {
            "attributes": [
                {
                    "attribute_code": "panel_type",
                    "label": "Panel Type",
                    "data_type": "select",
                    "hub_path_slugs": ["interior-doors"],
                    "disabled_channels": ["magento"],
                    "values": [{"value": "Flat"}],
                }
            ]
        },
    )

    result = publish_manual_attributes(session)

    assert result["filter_sync"]["attribute_count"] == 0
    assert enabled_filters_for_path_slug(session, "interior-doors") == []


def test_list_master_attribute_values_uses_locked_vocabulary(catalog_intent_session):
    session = catalog_intent_session
    ensure_default_manual_attributes(session)
    # Pollute DB with junk distincts — locked vocab must hide them.
    product = MasterProduct(sku="ACH-B12", name="B12", assembly_type="bundle", is_active=True, row_hash="x")
    session.add(product)
    session.flush()
    session.add(
        MasterProductAttributeValue(
            product_id=product.id,
            sku=product.sku,
            attribute_code="assembled_or_rta",
            value="bundle",
            source_label="test",
        )
    )
    session.flush()

    values = list_master_attribute_values(session, "assembled_or_rta")
    assert values == ["Assembled", "Unassembled/RTA"]
    assert list_master_attribute_values(session, "assembly_type") == ["Assembled", "Unassembled/RTA"]


def test_import_maps_and_drops_polluted_assembled_or_rta(catalog_intent_session):
    session = catalog_intent_session
    ensure_default_manual_attributes(session)

    result = import_master_sku_dataframe(
        session,
        pd.DataFrame(
            [
                {
                    "sku": "ACH-W0930",
                    "Item": "W0930",
                    "Category": "Kitchen Cabinets : Port & Bell : Anna Caramel Harvest",
                    "Product Category": "Kitchen Cabinets",
                    "Item Style": "Anna Caramel Harvest - Assembled",
                    "Assembled or RTA": "RTA",
                },
                {
                    "sku": "ACH-W0930-A",
                    "Item": "W0930",
                    "Category": "Kitchen Cabinets : Port & Bell : Anna Caramel Harvest",
                    "Product Category": "Kitchen Cabinets",
                    "Item Style": "Anna Caramel Harvest - Assembled",
                    "Assembled or RTA": "Assembled",
                },
                {
                    "sku": "ACH-PKG",
                    "Item": "PKG",
                    "Category": "Kitchen Cabinets : Port & Bell : Anna Caramel Harvest",
                    "Product Category": "Kitchen Cabinets",
                    "Item Style": "Anna Caramel Harvest - Assembled",
                    "Assembled or RTA": "bundle",
                },
            ]
        ),
        source_label="test_manual_attr_vocab",
        reconcile_taxonomy=False,
    )
    assert result["inserted"] == 3

    values = {
        row.sku: row.value
        for row in session.scalars(
            select(MasterProductAttributeValue).where(
                MasterProductAttributeValue.attribute_code == "assembled_or_rta"
            )
        ).all()
    }
    assert values["ACH-W0930"] == "Unassembled/RTA"
    assert values["ACH-W0930-A"] == "Assembled"
    assert "ACH-PKG" not in values

    products = {
        row.sku: row.assembly_type
        for row in session.scalars(select(MasterProduct).where(MasterProduct.sku.in_(list(values) + ["ACH-PKG"]))).all()
    }
    assert products["ACH-W0930"] == "RTA"
    assert products["ACH-W0930-A"] == "Assembled"
    # bundle dropped from attr; assembly_type may still derive from Item Style heuristics
    assert products["ACH-PKG"] in {None, "Assembled"}


def test_default_manual_attributes_contract():
    assert DEFAULT_MANUAL_ATTRIBUTES[0]["attribute_code"] == "assembled_or_rta"
    assert [v["value"] for v in DEFAULT_MANUAL_ATTRIBUTES[0]["values"]] == [
        "Assembled",
        "Unassembled/RTA",
    ]


def test_strip_hub_scoped_fields_keeps_matching_hub_only():
    from db.manual_attributes import strip_hub_scoped_fields

    rules = [
        {
            "attribute_code": "vanity_finish",
            "covered_codes": ["vanity_finish"],
            "hub_path_slugs": ["bathroom-vanities"],
            "taxonomy_path_slugs": [],
            "collection_codes": [],
            "is_global": False,
        },
        {
            "attribute_code": "door_style",
            "covered_codes": ["door_style"],
            "hub_path_slugs": ["kitchen-cabinets"],
            "taxonomy_path_slugs": [],
            "collection_codes": [],
            "is_global": False,
        },
    ]
    fields = {
        "title": "Vanity 36",
        "vanity_finish": "Matte White",
        "door_style": "Shaker",
        "brand": "Dayton",
    }
    strip_hub_scoped_fields(
        fields,
        path_slugs=["bathroom-vanities/dayton"],
        scoped_rules=rules,
    )
    assert fields["vanity_finish"] == "Matte White"
    assert "door_style" not in fields
    assert fields["brand"] == "Dayton"


def test_build_channel_product_payloads_strips_out_of_hub_manual_attributes(catalog_intent_session):
    from db.channel_exports import build_channel_product_payloads
    from db.models import MasterProductAttributeValue

    session = catalog_intent_session
    replace_manual_attributes(
        session,
        {
            "attributes": [
                {
                    "attribute_code": "vanity_finish",
                    "label": "Vanity Finish",
                    "data_type": "select",
                    "hub_path_slugs": ["bathroom-vanities"],
                    "values": [{"value": "Matte White"}],
                }
            ]
        },
    )
    kitchen = MasterProduct(
        sku="KC-1",
        name="Kitchen SKU",
        category_l1="Kitchen Cabinets",
        collection="Anna",
        is_active=True,
        row_hash="k1",
    )
    vanity = MasterProduct(
        sku="BV-1",
        name="Vanity SKU",
        category_l1="Bathroom Vanities",
        collection="Dayton",
        is_active=True,
        row_hash="b1",
    )
    session.add_all([kitchen, vanity])
    session.flush()
    session.add_all(
        [
            MasterProductAttributeValue(
                product_id=kitchen.id,
                sku=kitchen.sku,
                attribute_code="vanity_finish",
                value="Matte White",
                source_label="test",
            ),
            MasterProductAttributeValue(
                product_id=vanity.id,
                sku=vanity.sku,
                attribute_code="vanity_finish",
                value="Matte White",
                source_label="test",
            ),
        ]
    )
    session.flush()

    payloads = {
        row["sku"]: row
        for row in build_channel_product_payloads(
            session,
            "magento",
            skus=["KC-1", "BV-1"],
            only_assigned=False,
            prefer_projection_cache=False,
            skip_taxonomy=True,
        )
    }
    assert "vanity_finish" not in (payloads["KC-1"]["canonical_fields"] or {})
    assert "vanity_finish" not in (payloads["KC-1"]["fields"] or {})
    assert payloads["BV-1"]["canonical_fields"]["vanity_finish"] == "Matte White"
    assert payloads["BV-1"]["fields"]["vanity_finish"] == "Matte White"


def test_publish_manual_attributes_ensures_magento_alias_without_connection(catalog_intent_session):
    from db.models import ChannelAttributeAlias

    session = catalog_intent_session
    replace_manual_attributes(
        session,
        {
            "attributes": [
                {
                    "attribute_code": "vanity_finish",
                    "label": "Vanity Finish",
                    "data_type": "select",
                    "hub_path_slugs": ["bathroom-vanities"],
                    "values": [{"value": "Matte White"}],
                }
            ]
        },
    )
    result = publish_manual_attributes(session, provision_magento=True, push_filters=True)
    assert result["published_codes"] == ["vanity_finish"]
    assert "vanity_finish" in result["alias_sync"]["ensured_codes"]
    assert result["provision"]["status"] == "skipped"
    alias = session.scalar(
        select(ChannelAttributeAlias)
        .where(ChannelAttributeAlias.channel_code == "magento")
        .where(ChannelAttributeAlias.canonical_code == "vanity_finish")
        .limit(1)
    )
    assert alias is not None
    assert alias.action == "create_new"
    assert (alias.transform_rule or {}).get("sync_options_from_master") is True

