from __future__ import annotations import json from decimal import Decimal from pathlib import Path from typing import Any from ._repo_root import ensure_repo_root_on_syspath from .models import PricingModel, Product ensure_repo_root_on_syspath() from adaptive_pricing_core.provider_publication import ( # noqa: E402 CatalogProduct, ProviderPublicationState, apply_publication, build_publication_bundle, plan_publication, provider_state_from_dict, provider_state_to_dict, rollback_publication, ) from adaptive_pricing_core.stripe_provider import map_bundle_to_stripe # noqa: E402 def _serialize(value: Any) -> Any: if isinstance(value, Decimal): return str(value) if hasattr(value, "__dataclass_fields__"): return {key: _serialize(getattr(value, key)) for key in value.__dataclass_fields__} if isinstance(value, tuple): return [_serialize(item) for item in value] if isinstance(value, list): return [_serialize(item) for item in value] if isinstance(value, dict): return {key: _serialize(item) for key, item in value.items()} return value def default_stripe_state_path(data_dir: Path) -> Path: return data_dir / "provider_state" / "stripe-publication.json" def load_stripe_publication_state(path: Path) -> ProviderPublicationState: if not path.exists(): return ProviderPublicationState(provider="stripe") return provider_state_from_dict(json.loads(path.read_text(encoding="utf-8"))) def save_stripe_publication_state(path: Path, state: ProviderPublicationState) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( json.dumps(provider_state_to_dict(state), indent=2), encoding="utf-8", ) def _catalog_product(product: Product) -> CatalogProduct: return CatalogProduct( id=product.id, name=product.name, description=product.description, currency=product.currency, lifecycle_phase=product.lifecycle_phase, active_pricing_model_id=product.active_pricing_model_id, metadata={"product_channel": "membership"}, ) def _target_model( models: list[PricingModel], product: Product, model_id: str | None = None, ) -> PricingModel: requested_id = model_id or product.active_pricing_model_id return next(item for item in models if item.id == requested_id) def build_stripe_publication_preview( product: Product, models: list[PricingModel], data_dir: Path, *, model_id: str | None = None, state_path: Path | None = None, ) -> dict[str, Any]: model = _target_model(models, product, model_id) bundle = build_publication_bundle(_catalog_product(product), model) package = map_bundle_to_stripe(bundle) state = load_stripe_publication_state(state_path or default_stripe_state_path(data_dir)) plan = plan_publication(package, state) return _serialize( { "provider": "stripe", "state_path": str(state_path or default_stripe_state_path(data_dir)), "model_id": model.id, "model_name": model.name, "current_state": { "active_revision_id": state.active_revision_id, "active_model_id": state.active_model_id, "artifact_count": len(state.artifacts), "revision_count": len(state.revisions), }, "artifact_counts": { "exact": sum(item.mapping_status == "exact" for item in package.artifacts), "approximate": sum(item.mapping_status == "approximate" for item in package.artifacts), "unsupported": sum(item.mapping_status == "unsupported" for item in package.artifacts), }, "plan": plan, "notes": package.notes, } ) def publish_to_stripe_shadow( product: Product, models: list[PricingModel], data_dir: Path, *, model_id: str | None = None, state_path: Path | None = None, ) -> dict[str, Any]: path = state_path or default_stripe_state_path(data_dir) model = _target_model(models, product, model_id) bundle = build_publication_bundle(_catalog_product(product), model) package = map_bundle_to_stripe(bundle) current_state = load_stripe_publication_state(path) result = apply_publication(package, current_state) save_stripe_publication_state(path, result.state) return _serialize( { "provider": "stripe", "state_path": str(path), "model_id": model.id, "model_name": model.name, "result": result, } ) def rollback_stripe_shadow( data_dir: Path, revision_id: str, *, state_path: Path | None = None, ) -> dict[str, Any]: path = state_path or default_stripe_state_path(data_dir) current_state = load_stripe_publication_state(path) result = rollback_publication(current_state, revision_id) save_stripe_publication_state(path, result.state) return _serialize( { "provider": "stripe", "state_path": str(path), "result": result, } )