"""loyalty 003 - add CHECK constraints on balances and program limits

Defends against direct SQL writes that would create impossible loyalty
state (negative balances, zero stamps target, etc). The application
service layer already clamps these values, but a database-level guard
catches anything that bypasses the service (manual fixes, scripts, ORM
shortcuts) before it can corrupt downstream reports and exports.

Pre-flight check on the dev database showed zero existing violations
(see Phase 1.5 of the loyalty production launch plan), so no data
remediation is needed in this upgrade path.

Revision ID: loyalty_003
Revises: loyalty_002
Create Date: 2026-04-09
"""
from alembic import op

revision = "loyalty_003"
down_revision = "loyalty_002"
branch_labels = None
depends_on = None


CONSTRAINTS = [
    ("loyalty_cards", "ck_loyalty_cards_points_balance_nonneg", "points_balance >= 0"),
    ("loyalty_cards", "ck_loyalty_cards_stamp_count_nonneg", "stamp_count >= 0"),
    (
        "loyalty_programs",
        "ck_loyalty_programs_min_purchase_nonneg",
        "minimum_purchase_cents >= 0",
    ),
    (
        "loyalty_programs",
        "ck_loyalty_programs_points_per_euro_nonneg",
        "points_per_euro >= 0",
    ),
    (
        "loyalty_programs",
        "ck_loyalty_programs_stamps_target_positive",
        "stamps_target >= 1",
    ),
    (
        "loyalty_programs",
        "ck_loyalty_programs_welcome_bonus_nonneg",
        "welcome_bonus_points >= 0",
    ),
]


def upgrade() -> None:
    for table, name, expression in CONSTRAINTS:
        op.create_check_constraint(name, table, expression)


def downgrade() -> None:
    for table, name, _expression in reversed(CONSTRAINTS):
        op.drop_constraint(name, table, type_="check")
