#!/usr/bin/env python3
"""Fictitious Adobe Commerce order-line selection, Python >= 3.10.

No HTTP requests, invoice creation, payment capture or ERP writes. Available
quantities are supplied fixture decisions, not calculated native eligibility.
The deliberately narrow fixture accepts simple and configurable order lines.
"""
from copy import deepcopy
from dataclasses import dataclass
from hashlib import sha256
import json


def positive_integer(value) -> bool:
    return type(value) is int and value > 0


def order_index(order_id: int, lines: list[dict]) -> dict[int, dict]:
    if not positive_integer(order_id) or not lines:
        raise ValueError("Complete identified order snapshot required")
    result = {}
    for line in lines:
        item_id = line.get("item_id")
        if not positive_integer(item_id) or item_id in result:
            raise ValueError("Invalid or duplicate order line identity")
        if type(line.get("order_id")) is not int or line["order_id"] != order_id:
            raise ValueError("Line belongs to another order")
        if line.get("product_type") not in {"simple", "configurable"}:
            raise ValueError("Product type outside fixture scope")
        if not isinstance(line.get("sku"), str) or not line["sku"].strip():
            raise ValueError("Sold SKU required")
        result[item_id] = dict(line)
    for line in result.values():
        parent_id = line.get("parent_item_id")
        if parent_id is not None:
            if not positive_integer(parent_id) or parent_id not in result:
                raise ValueError("Unresolved parent order line")
            parent = result[parent_id]
            if line["product_type"] != "simple" or parent["product_type"] != "configurable":
                raise ValueError("Unsupported parent-child relationship")
        elif line["product_type"] == "configurable":
            children = [child for child in result.values() if child.get("parent_item_id") == line["item_id"]]
            if len(children) != 1:
                raise ValueError("Fixture requires exactly one sold simple child")
    return result


@dataclass(frozen=True)
class SelectedLine:
    source_item_id: int
    quantity: int


def invoice_target(index: dict[int, dict], source_item_id: int) -> tuple[int, str]:
    if not positive_integer(source_item_id) or source_item_id not in index:
        raise ValueError("Order item identity required, not catalog product ID")
    line = index[source_item_id]
    if line.get("parent_item_id") is not None:
        return line["parent_item_id"], line["sku"]
    if line["product_type"] == "configurable":
        child = next(child for child in index.values() if child.get("parent_item_id") == source_item_id)
        return source_item_id, child["sku"]
    return source_item_id, line["sku"]


def prepare(order_id: int, lines: list[dict], selections: list[SelectedLine],
            available: dict[int, int]) -> dict:
    index = order_index(order_id, lines)
    if not selections:
        raise ValueError("Explicit non-empty partial selection required")
    items, lineage, targets = [], [], set()
    for selection in selections:
        if not positive_integer(selection.quantity):
            raise ValueError("Positive whole-piece fixture quantity required")
        target, sold_sku = invoice_target(index, selection.source_item_id)
        if target in targets:
            raise ValueError("Parent and child cannot create two selected effects")
        capacity = available.get(target)
        if not positive_integer(capacity) or selection.quantity > capacity:
            raise ValueError("Supplied available quantity missing or insufficient")
        targets.add(target)
        items.append({"order_item_id": target, "qty": selection.quantity})
        lineage.append({"order_id": order_id, "source_item_id": selection.source_item_id,
                        "invoice_order_item_id": target, "sold_sku": sold_sku})
    return {"items": sorted(items, key=lambda item: item["order_item_id"]),
            "lineage": sorted(lineage, key=lambda item: item["invoice_order_item_id"])}


def candidate_fingerprint(installation: str, order_id: int, candidate: dict) -> str:
    if not isinstance(installation, str) or not installation.strip() or not positive_integer(order_id):
        raise ValueError("Identified source installation and order required")
    return sha256(json.dumps({"installation": installation, "order_id": order_id, "items": candidate["items"]},
                             sort_keys=True, separators=(",", ":")).encode()).hexdigest()


def unique_sku_lookup(index: dict[int, dict], sku: str) -> int:
    matches = [item_id for item_id, line in index.items() if line["sku"] == sku]
    if len(matches) != 1:
        raise ValueError("SKU does not identify one order line")
    return matches[0]


def main() -> None:
    checks = 0

    def check(condition: bool, label: str) -> None:
        nonlocal checks
        if not condition:
            raise AssertionError(label)
        checks += 1
        print("PASS", label)

    def refuses(action, label: str) -> None:
        try:
            action()
        except ValueError:
            check(True, label)
        else:
            raise AssertionError("Expected refusal: " + label)

    lines = [
        {"item_id": 501, "order_id": 7000, "product_id": 80, "sku": "TEE-M", "product_type": "configurable", "parent_item_id": None},
        {"item_id": 502, "order_id": 7000, "product_id": 81, "sku": "TEE-M", "product_type": "simple", "parent_item_id": 501},
        {"item_id": 511, "order_id": 7000, "product_id": 80, "sku": "TEE-L", "product_type": "configurable", "parent_item_id": None},
        {"item_id": 512, "order_id": 7000, "product_id": 82, "sku": "TEE-L", "product_type": "simple", "parent_item_id": 511},
        {"item_id": 601, "order_id": 7000, "product_id": 90, "sku": "BAG", "product_type": "simple", "parent_item_id": None},
    ]
    original = deepcopy(lines)
    index = order_index(7000, lines)
    capacity = {501: 2, 511: 1, 601: 3}  # Supplied fixture decisions only.
    check(invoice_target(index, 502) == (501, "TEE-M"), "sold child resolves to its invoice parent")
    check(invoice_target(index, 512) == (511, "TEE-L"), "second variant retains its own parent")
    check(invoice_target(index, 501) == (501, "TEE-M"), "explicit parent keeps the sold child SKU")
    check(invoice_target(index, 601) == (601, "BAG"), "standalone simple keeps its own line identity")
    chosen = [SelectedLine(512, 1), SelectedLine(601, 2)]
    candidate = prepare(7000, lines, chosen, capacity)
    check(candidate["items"] == [{"order_item_id": 511, "qty": 1}, {"order_item_id": 601, "qty": 2}],
          "partial candidate contains only intended invoice lines")
    check(all(item["order_item_id"] not in {502, 512} for item in candidate["items"]),
          "configurable child IDs excluded from candidate items")
    check(candidate["lineage"][0]["source_item_id"] == 512 and candidate["lineage"][0]["sold_sku"] == "TEE-L",
          "ERP lineage preserves the selected physical variant")
    check(prepare(7000, lines, [SelectedLine(501, 1)], capacity)["items"] ==
          prepare(7000, lines, [SelectedLine(502, 1)], capacity)["items"],
          "parent and child selections resolve to the same single target")
    refuses(lambda: prepare(7000, lines, [SelectedLine(501, 1), SelectedLine(502, 1)], capacity),
            "duplicate parent-child effect refused rather than summed")
    refuses(lambda: invoice_target(index, 80), "catalog product ID is not an order item ID")
    refuses(lambda: invoice_target(index, 999), "unknown order item refused")
    refuses(lambda: unique_sku_lookup(index, "TEE-M"), "same parent-child SKU cannot select a line")
    check(unique_sku_lookup(index, "BAG") == 601, "unique fixture SKU lookup remains separate from mapping")
    for quantity in (0, -1, True, "1", 1.5):
        refuses(lambda quantity=quantity: prepare(7000, lines, [SelectedLine(502, quantity)], capacity),
                "unsupported fixture quantity refused: " + repr(quantity))
    refuses(lambda: prepare(7000, lines, [SelectedLine(512, 2)], capacity), "selected quantity exceeds supplied availability")
    refuses(lambda: prepare(7000, lines, [SelectedLine(512, 1)], {}), "missing availability blocks candidate")
    refuses(lambda: prepare(7000, lines, [], capacity), "empty partial selection refused")
    refuses(lambda: order_index(7000, lines + [lines[0]]), "duplicate order item identity refused")
    refuses(lambda: order_index(7000, [dict(item, order_id=7001) if item["item_id"] == 502 else item for item in lines]),
            "parent-child graph cannot cross order identity")
    refuses(lambda: order_index(7000, [dict(item, parent_item_id=999) if item["item_id"] == 502 else item for item in lines]),
            "unresolved parent blocks complete snapshot")
    refuses(lambda: order_index(7000, [dict(item, parent_item_id=502) if item["item_id"] == 502 else item for item in lines]),
            "self-parent relationship refused")
    refuses(lambda: order_index(7000, [dict(item, product_type="bundle") if item["item_id"] == 501 else item for item in lines]),
            "bundle rule not inferred from configurable rule")
    refuses(lambda: order_index(7000, [item for item in lines if item["item_id"] != 502]),
            "configurable without sold child remains unresolved")
    refuses(lambda: order_index(7000, lines + [dict(lines[1], item_id=503)]),
            "multiple children require a different qualified contract")
    check(lines == original, "source order snapshot is never rewritten")
    reversed_candidate = prepare(7000, list(reversed(lines)), list(reversed(chosen)), capacity)
    check(candidate_fingerprint("fixture-shop-A", 7000, candidate) == candidate_fingerprint("fixture-shop-A", 7000, reversed_candidate),
          "candidate identity does not depend on array order")
    check(candidate_fingerprint("fixture-shop-A", 7000, candidate) != candidate_fingerprint("fixture-shop-A", 7001, candidate),
          "candidate fingerprint includes order identity")
    check(candidate_fingerprint("fixture-shop-A", 7000, candidate) != candidate_fingerprint("fixture-shop-B", 7000, candidate),
          "candidate fingerprint separates source installations")
    changed = prepare(7000, lines, [SelectedLine(512, 1), SelectedLine(601, 1)], capacity)
    check(candidate_fingerprint("fixture-shop-A", 7000, candidate) != candidate_fingerprint("fixture-shop-A", 7000, changed),
          "quantity change produces a different candidate fingerprint")
    print("CHECKS", checks)
    print("Adobe Commerce requests: 0; invoices created: 0; payment captures: 0; ERP writes: 0.")


if __name__ == "__main__":
    main()
