"""Repair kitchen_cabinets_taxonomy.json and sync Brochure_Data taxonomy_groups."""
from __future__ import annotations

import json
import re
from collections import defaultdict
from copy import deepcopy
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple


TAXONOMY_PATH = Path(r"E:\2026\plytixmage_dbs_data\kitchen_cabinets_taxonomy.json")
BROCHURE_DIR = Path(r"E:\2026\plytixmage_dbs_data\Brochure_Data")
REPORT_PATH = Path(r"E:\2026\plytixmage_dbs_data\audit_reports\taxonomy_repair_report.json")


CONFIRMED_HIERARCHY = {
    "Base Cabinets": [
        "Angle End Base Cabinet",
        "Basic Base",
        "Blind Corner Base Cabinet",
        "Corner Base",
        "Corner Sink Base Cabinet",
        "Double Door Base Cabinet",
        "Drawer Base",
        "Microwave Base Cabinet",
        "Single Door",
        "Sink Base",
        "Special Base",
    ],
    "Wall Cabinets": [
        "Angle End Wall Cabinet",
        "Basic Wall",
        "Blind Corner Wall Cabinet",
        "Bridge Wall",
        "Corner Wall",
        "Diagonal Corner Glass Wall Cabinet",
        "Double Door Glass Wall Cabinet",
        "Double Door Wall Cabinet",
        "Microwave Shelf Cabinet",
        "Open Diagonal Corner Wall Cabinet",
        "Open Pie Cut Corner Wall Cabinet",
        "Open Wall Cabinet",
        "Range Hood Cabinet",
        "Single Door Glass Wall Cabinet",
        "Single Door Wall Cabinet",
        "Special Wall",
        "Stacked Wall",
        "Wine Rack Cabinet",
    ],
    "Tall Cabinets": [
        "Oven Cabinet",
        "Pantry",
        "Special Tall",
    ],
    "Panels And Fillers": [
        "End Panel",
        "Fillers",
        "Finished Plywood Panel",
        "Panels",
        "Skin Panel",
    ],
    "Mouldings": [
        "Additional Moldings",
        "Bead Molding",
        "Cove Crown",
        "Crown Molding",
        "Furniture Base",
        "Inside Corner",
        "Light Molding",
        "Outside Corner",
        "Scribe",
        "Shoe",
        "Toe Kick",
    ],
    "Accessories": [
        "Clearance Kit",
        "Decorative Trim",
        "Floating Shelf",
        "Loose Glass Door",
        "Loose Solid Door",
        "Pantry Pullout",
        "Sample Door",
        "Touch Up Paint",
        "Trash Storage",
    ],
}


CROSS_L2_NOTES = [
    "Skin Panel exists only under Panels And Fillers for assignment purposes. Accessories > Skin Panel is intentionally unused.",
    "Toe Kick molding SKUs (e.g. TK8) assign to Mouldings > Toe Kick, not Accessories.",
    "Trash Pullout bases (e.g. BWB18) appear under Special Bases in brochures but assign to Accessories > Trash Storage.",
    "GDW* basic-wall glass cabinets assign to Single/Double Door Glass Wall Cabinet, never Basic Wall.",
    "GD-W* loose glass doors assign to Accessories > Loose Glass Door.",
    "Bridge Wall is a confirmed Wall Cabinets L3 (master node exists; may not yet be pushed to Magento).",
    "Solid basic walls stay under Wall Cabinets > Basic Wall; solid basic bases stay under Base Cabinets > Basic Base.",
    "Clearance Kit (and other Accessories L3s) are product taxonomy leaves under kitchen-cabinets/accessories/*, never finish collections. Do not create Start Shopping / collection landings or nested paths like accessories/clearance-kit/base-cabinets.",
]


def _slug(text: str) -> str:
    return re.sub(r"[^a-z0-9]+", "-", text.strip().lower()).strip("-")


def classify_moulding_l3(sku: str, source_sku_type: Optional[str] = None) -> Optional[str]:
    code = str(sku or "").strip().upper()
    st = str(source_sku_type or "").strip().lower()
    if code.startswith("TKBM") or "furniture_base" in st:
        return "Furniture Base"
    if re.match(r"^TK\d", code) or st in {"toe_kick_molding", "scribe_toe_kick_molding"}:
        # Belmont-style combined scribe/toe-kick still lives as Toe Kick per molding family.
        return "Toe Kick"
    if code.startswith(("OCM",)) or "outside_corner" in st:
        return "Outside Corner"
    if code.startswith(("ICM",)) or "inside_corner" in st:
        return "Inside Corner"
    if code.startswith(("SHM",)) or "shoe" in st:
        return "Shoe"
    if code.startswith(("SBM",)) or re.match(r"^BM\d+$", code) or st in {"single_bead_molding", "bead_molding"}:
        return "Bead Molding"
    if code.startswith(("CVM", "LCVM")) or "cove" in st:
        return "Cove Crown"
    if code.startswith(("LRM", "LM", "LRD", "LRS")) or "light" in st:
        return "Light Molding"
    if code.startswith(("TLM", "CM", "LGCROWN", "SMCROWN", "CMC", "CMA", "CMV")) or "crown" in st:
        return "Crown Molding"
    if code.startswith("SM") and not code.startswith(("SMCROWN", "SBM")):
        return "Scribe"
    if code.startswith(("QRM",)):
        return "Additional Moldings"
    return None


def desired_assignment(item: Dict[str, Any]) -> Tuple[str, str, Optional[str]]:
    sku = str(item.get("sku") or "").strip()
    code = sku.upper()
    l2 = str(item.get("l2_category") or "").strip()
    l3 = str(item.get("l3_category") or "").strip()
    fam = str(item.get("source_family") or "").strip().lower()
    st = str(item.get("source_sku_type") or "").strip().lower()
    note = item.get("note")

    # Glass wall cabinets from basic_wall family
    if fam == "basic_wall" and (st == "wall_cabinet_glass" or code.startswith("GDW")):
        door = None
        # Prefer existing correct L3; else infer from width digits.
        if l3 in {"Single Door Glass Wall Cabinet", "Double Door Glass Wall Cabinet"}:
            return "Wall Cabinets", l3, None
        m = re.search(r"GDW(\d{2})(30|36|42)$", code)
        if m:
            width = int(m.group(1))
            leaf = "Single Door Glass Wall Cabinet" if width <= 21 else "Double Door Glass Wall Cabinet"
            return "Wall Cabinets", leaf, None

    # Loose glass doors
    if code.startswith("GD-W") or st.startswith("loose_glass_door"):
        return "Accessories", "Loose Glass Door", None

    # Skin panels always Panels And Fillers
    if "SKIN" in code or st.endswith("skin_panel") or "skin_panel" in st:
        return "Panels And Fillers", "Skin Panel", None

    # Microwave bases must never be mistaken for bead molding (BM* prefix collision).
    if st == "microwave_base" or code.startswith("BMC"):
        return "Base Cabinets", "Microwave Base Cabinet", None

    # Panel SKUs that may have been corrupted into mouldings by BM* prefix matching.
    if fam == "panels_and_fillers" or st.endswith("_panel") or re.match(r"^(BF|F\d|FO\d|FF\d|PNL|BPNL|DWR|REP|UREP|MBEP|MWEP|MPBEP|FPP)", code):
        if "SKIN" in code:
            return "Panels And Fillers", "Skin Panel", None
        if code.startswith(("BF", "F")) and not code.startswith(("FS", "FPP")):
            # fillers often F330 etc; FPP is finished plywood
            if code.startswith("FPP"):
                return "Panels And Fillers", "Finished Plywood Panel", None
            if re.match(r"^F\d", code) or code.startswith("BF"):
                return "Panels And Fillers", "Fillers", None
        if code.startswith(("MBEP", "MWEP", "MPBEP", "DWR", "REP", "BPNL", "PNL")):
            return "Panels And Fillers", "Panels", None
        if l2 == "Panels And Fillers" and l3:
            return l2, l3, None

    # Trash pullout
    if code.startswith("BWB") or st == "trash_pullout_base" or l3 == "Trash Storage":
        return (
            "Accessories",
            "Trash Storage",
            "Brochures list this under Special Bases; assigned to Accessories > Trash Storage by confirmed taxonomy rule.",
        )

    # Mouldings (including TK formerly under Accessories)
    is_moulding_row = (
        fam in {"moldings", "mouldings"}
        or (l2 == "Mouldings" and not code.startswith("BMC") and "PNL" not in code)
        or st.endswith("_molding")
        or ("molding" in st and st != "microwave_base")
        or (l3 == "Toe Kick" and (code.startswith("TK") or "toe" in st))
    )
    if is_moulding_row:
        mould_l3 = classify_moulding_l3(code, st)
        if mould_l3:
            return "Mouldings", mould_l3, None
        if l2 == "Mouldings" and l3 and l3 != "Toe Kick":
            return "Mouldings", l3, None
        return "Mouldings", "Additional Moldings", None

    # Full-height base doors (BH*) are Basic Bases in the brochure, not loose accessory doors.
    if code.startswith("BH") or st in {"full_height_door", "full_height_base"}:
        return "Base Cabinets", "Basic Base", None

    # Loose solid doors previously parked under Decorative Trim (exclude BH full-height bases)
    if note and "Loose Solid Door" in str(note) and not code.startswith("BH"):
        return (
            "Accessories",
            "Loose Solid Door",
            "Loose solid replacement door (not glass, not a full cabinet).",
        )

    # Keep solid basic walls / bases as confirmed
    if fam == "basic_wall" and st == "wall_cabinet":
        return "Wall Cabinets", "Basic Wall", None
    if fam == "basic_bases":
        return "Base Cabinets", "Basic Base", None

    return l2, l3, note if isinstance(note, str) else note


def repair_taxonomy(payload: Dict[str, Any]) -> Dict[str, Any]:
    out = deepcopy(payload)
    ref = out.setdefault("master_taxonomy_reference", {})
    ref["confirmed_hierarchy"] = deepcopy(CONFIRMED_HIERARCHY)
    ref["proposed_new_l3_categories"] = {
        "Wall Cabinets": [
            "Plate Rack Cabinet",
            "Organizer Cabinet",
            "End Shelf Cabinet",
            "Microwave Cabinet (Enclosed)",
        ],
    }
    ref["cross_l2_notes"] = list(CROSS_L2_NOTES)

    changes: List[Dict[str, Any]] = []
    for coll_code, coll in (out.get("collections") or {}).items():
        assignments = coll.get("assignments") or []
        for item in assignments:
            before = (item.get("l2_category"), item.get("l3_category"), item.get("note"))
            l2, l3, note = desired_assignment(item)
            item["l2_category"] = l2
            item["l3_category"] = l3
            item["note"] = note
            after = (l2, l3, note)
            if before != after:
                changes.append(
                    {
                        "collection": coll_code,
                        "sku": item.get("sku"),
                        "before": {"l2": before[0], "l3": before[1], "note": before[2]},
                        "after": {"l2": after[0], "l3": after[1], "note": after[2]},
                    }
                )
        coll["sku_count"] = len(assignments)
    return out, changes


def build_taxonomy_groups(assignments: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    grouped: Dict[Tuple[str, str], Dict[str, Any]] = {}
    for item in assignments:
        l2 = str(item.get("l2_category") or "").strip()
        l3 = str(item.get("l3_category") or "").strip()
        sku = str(item.get("sku") or "").strip()
        if not (l2 and l3 and sku):
            continue
        key = (l2, l3)
        group = grouped.setdefault(
            key,
            {
                "brochure_l1_label": l2,
                "brochure_l2_label": l3,
                "brochure_leaf_label": None,
                "canonical_taxonomy_path": f"Kitchen Cabinets/{l2}/{l3}",
                "canonical_taxonomy_path_label": f"Kitchen Cabinets/{l2}/{l3}",
                "canonical_taxonomy_path_slug": _slug(f"kitchen-cabinets/{l2}/{l3}").replace("-cabinets-", "-cabinets/").replace("panels-and-fillers", "panels-and-fillers"),
                "skus": [],
            },
        )
        # Prefer normalize_plp_path style manually
        group["canonical_taxonomy_path_slug"] = (
            "kitchen-cabinets/"
            + _slug(l2)
            + "/"
            + _slug(l3)
        )
        # Fix panels and fillers slug wording to match existing convention
        if l2 == "Panels And Fillers":
            group["canonical_taxonomy_path_slug"] = f"kitchen-cabinets/panels-and-fillers/{_slug(l3)}"
        if l2 == "Mouldings":
            group["canonical_taxonomy_path_slug"] = f"kitchen-cabinets/mouldings/{_slug(l3)}"
        group["skus"].append(sku)

    groups = list(grouped.values())
    for group in groups:
        group["skus"] = sorted(set(group["skus"]))
    groups.sort(key=lambda g: (g["brochure_l1_label"], g["brochure_l2_label"]))
    return groups


def sync_brochure_files(taxonomy: Dict[str, Any]) -> Dict[str, Any]:
    collections = taxonomy.get("collections") or {}
    updated = []
    skipped = []
    for path in sorted(BROCHURE_DIR.glob("*.json")):
        payload = json.loads(path.read_text(encoding="utf-8"))
        collection = payload.get("collection") if isinstance(payload.get("collection"), dict) else {}
        canonical = str(collection.get("canonical_code") or "").strip()
        entry = collections.get(canonical)
        if not isinstance(entry, dict):
            skipped.append({"file": path.name, "canonical_code": canonical, "reason": "no_taxonomy_collection"})
            continue
        groups = build_taxonomy_groups(entry.get("assignments") or [])
        payload["taxonomy_groups"] = groups
        hints = payload.setdefault("normalization_seed_hints", {})
        if not isinstance(hints, dict):
            hints = {}
            payload["normalization_seed_hints"] = hints
        hints["taxonomy_source"] = "kitchen_cabinets_taxonomy.json"
        hints["taxonomy_synced_on"] = "2026-07-24"
        path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
        updated.append(
            {
                "file": path.name,
                "canonical_code": canonical,
                "group_count": len(groups),
                "sku_count": sum(len(g["skus"]) for g in groups),
            }
        )
    return {"updated": updated, "skipped": skipped}


def main() -> None:
    original = json.loads(TAXONOMY_PATH.read_text(encoding="utf-8"))
    repaired, changes = repair_taxonomy(original)
    TAXONOMY_PATH.write_text(json.dumps(repaired, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    brochure = sync_brochure_files(repaired)

    # Validation summary
    path_counts = defaultdict(int)
    for coll in repaired["collections"].values():
        for a in coll["assignments"]:
            path_counts[f"{a['l2_category']} > {a['l3_category']}"] += 1

    report = {
        "taxonomy_path": str(TAXONOMY_PATH),
        "assignment_changes": len(changes),
        "change_sample": changes[:50],
        "path_counts": dict(sorted(path_counts.items())),
        "brochure_sync": brochure,
        "confirmed_mouldings": CONFIRMED_HIERARCHY["Mouldings"],
        "confirmed_accessories": CONFIRMED_HIERARCHY["Accessories"],
    }
    REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
    REPORT_PATH.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    print(json.dumps({
        "assignment_changes": len(changes),
        "brochure_updated": len(brochure["updated"]),
        "brochure_skipped": len(brochure["skipped"]),
        "report": str(REPORT_PATH),
        "mouldings_paths": {k: v for k, v in path_counts.items() if k.startswith("Mouldings")},
        "tk_and_trash": {k: v for k, v in path_counts.items() if "Toe Kick" in k or "Trash" in k},
        "glass": {k: v for k, v in path_counts.items() if "Glass" in k},
    }, indent=2))


if __name__ == "__main__":
    main()
