#!/usr/bin/env python3
"""Local read planner and price-cache audit. No PrestaShop or ERP calls.

Python >= 3.10. All prices and identities are fictitious inputs. This is not
a native pricing engine, a credential handler or a production HTTP client.
"""
from dataclasses import asdict, dataclass, replace
from decimal import Decimal, InvalidOperation
from hashlib import sha256
from itertools import product
import json
from urllib.parse import parse_qs, urlencode, urlsplit


@dataclass(frozen=True)
class PriceContext:
    installation: str
    revision: str
    shop: int
    product: int
    attribute: int
    country: int
    currency: int
    group: int
    quantity: int
    state: int = 0
    decimals: int = 6
    use_reduction: bool = True
    use_ecotax: bool = True


def validate(context: PriceContext) -> None:
    for label in (context.installation, context.revision):
        if not isinstance(label, str) or not label.strip():
            raise ValueError("Explicit installation and price-policy revision required")
    for field in ("shop", "product", "country", "currency", "group", "quantity"):
        value = getattr(context, field)
        if type(value) is not int or value <= 0:
            raise ValueError("Positive integer required: " + field)
    for field in ("attribute", "state", "decimals"):
        value = getattr(context, field)
        if type(value) is not int or value < 0:
            raise ValueError("Non-negative integer required: " + field)
    if context.decimals > 6:
        raise ValueError("Fixture policy supports up to six decimals")
    if type(context.use_reduction) is not bool or type(context.use_ecotax) is not bool:
        raise ValueError("Explicit boolean pricing flags required")


def read_path(context: PriceContext) -> str:
    validate(context)
    parameters = {"id_shop": context.shop}
    for alias, tax in (("erp_ht", 0), ("erp_ttc", 1)):
        values = {
            "country": context.country, "state": context.state,
            "currency": context.currency, "group": context.group,
            "quantity": context.quantity, "product_attribute": context.attribute,
            "decimals": context.decimals, "use_tax": tax,
            "use_reduction": int(context.use_reduction), "only_reduction": 0,
            "use_ecotax": int(context.use_ecotax),
        }
        for name, value in values.items():
            parameters[f"price[{alias}][{name}]"] = value
    return f"/api/products/{context.product}?" + urlencode(parameters)


def cache_key(context: PriceContext, use_tax: bool) -> str:
    validate(context)
    if type(use_tax) is not bool:
        raise ValueError("Explicit tax mode required")
    data = asdict(context) | {"use_tax": use_tax}
    return sha256(json.dumps(data, sort_keys=True, separators=(",", ":")).encode()).hexdigest()


def price_value(raw: str) -> Decimal:
    if not isinstance(raw, str) or not raw.strip():
        raise ValueError("Decimal text required")
    try:
        value = Decimal(raw)
    except InvalidOperation as error:
        raise ValueError("Invalid decimal") from error
    if not value.is_finite() or value < 0:
        raise ValueError("Finite non-negative amount required")
    return value


@dataclass(frozen=True)
class Observation:
    key: str
    amount: str
    observed_at: int


def cached_value(observation: Observation, context: PriceContext,
                 use_tax: bool, now: int, max_age: int = 300) -> Decimal:
    if type(now) is not int or type(observation.observed_at) is not int:
        raise ValueError("Integer fixture clock required")
    if type(max_age) is not int or max_age <= 0:
        raise ValueError("Positive freshness limit required")
    age = now - observation.observed_at
    if observation.key != cache_key(context, use_tax) or age < 0 or age >= max_age:
        raise ValueError("Price context or freshness does not match")
    return price_value(observation.amount)


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)

    context = PriceContext("shop-fixture-A", "tariff-r4", 2, 80, 25, 8, 1, 3, 10)
    query = parse_qs(urlsplit(read_path(context)).query, strict_parsing=True)
    check(query["id_shop"] == ["2"], "legacy Webservice shop context retained")
    check(query["price[erp_ht][use_tax]"] == ["0"] and
          query["price[erp_ttc][use_tax]"] == ["1"], "HT and TTC aliases requested separately")
    check(query["price[erp_ht][quantity]"] == ["10"] and
          query["price[erp_ht][group]"] == ["3"], "quantity and customer group explicit")
    check(query["price[erp_ttc][only_reduction]"] == ["0"], "amount request is not reduction-only")
    check("shopId" not in query and "id_shop_group" not in query,
          "Admin API and specific-price resource names not used as shop query")
    first = cache_key(context, False)
    variants = {"installation": "shop-fixture-B", "revision": "tariff-r5", "shop": 3,
                "product": 81, "attribute": 26, "country": 9, "currency": 2,
                "group": 4, "quantity": 1, "state": 1, "decimals": 2,
                "use_reduction": False, "use_ecotax": False}
    for field, value in variants.items():
        check(cache_key(replace(context, **{field: value}), False) != first,
              "cache separates " + field)
    check(cache_key(context, True) != first, "cache separates tax modes")
    observation = Observation(first, "12.345600", 1000)
    check(cached_value(observation, context, False, 1299) == Decimal("12.345600"),
          "matching fresh observation preserves decimal precision")
    refuses(lambda: cached_value(observation, context, True, 1000), "HT cannot satisfy TTC read")
    refuses(lambda: cached_value(observation, replace(context, shop=3), False, 1000),
            "another shop cannot reuse observation")
    refuses(lambda: cached_value(observation, context, False, 1300), "exact freshness boundary rejected")
    refuses(lambda: cached_value(observation, context, False, 999), "future-dated observation rejected")
    refuses(lambda: cached_value(observation, replace(context, revision="tariff-r5"), False, 1000),
            "new policy revision invalidates old observation")
    for field, value in (("shop", 0), ("quantity", True), ("quantity", "10"),
                         ("group", 0), ("currency", 0), ("attribute", -1)):
        refuses(lambda field=field, value=value: read_path(replace(context, **{field: value})),
                "invalid context refused: " + field + "=" + repr(value))
    for raw in ("NaN", "Infinity", "-1", "", 12.5):
        refuses(lambda raw=raw: price_value(raw), "invalid amount refused: " + repr(raw))
    check(price_value("0.000000") == 0, "zero price remains an explicit amount")
    check(Decimal("12.345600") * 10 == Decimal("123.456000"), "unit value not rounded before multiplication")
    check(Decimal("12.35") * 10 != Decimal("123.456000"), "early rounding changes comparison")
    contexts = [replace(context, shop=shop, group=group, quantity=quantity)
                for shop, group, quantity in product((2, 3), (3, 4), (1, 10, 50))]
    check(len({read_path(item) for item in contexts}) == 12,
          "twelve explicit requests cover shop group quantity matrix")
    check(len({cache_key(item, tax) for item in contexts for tax in (False, True)}) == 24,
          "twenty-four observations keep all price contexts separate")
    print("CHECKS", checks)
    print("PrestaShop requests: 0; native price calculations: 0; ERP writes: 0.")


if __name__ == "__main__":
    main()
