from datetime import UTC, datetime, time, timedelta


def _aware(dt: datetime | None) -> datetime | None:
    """Ensure datetime is timezone-aware (handles pre-TIMESTAMPTZ databases)."""
    if dt is not None and dt.tzinfo is None:
        return dt.replace(tzinfo=UTC)
    return dt


from enum import Enum

from sqlalchemy import (
    JSON,
    BigInteger,
    Boolean,
    Column,
    Date,
    DateTime,
    Float,
    ForeignKey,
    Index,
    Integer,
    String,
    Table,
    Text,
    Time,
    TypeDecorator,
    UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Mapped, backref, mapped_column, relationship
from sqlalchemy.sql import func


class AwareDateTime(TypeDecorator):
    """DateTime that auto-converts naive values to UTC-aware on load from DB.

    Handles pre-TIMESTAMPTZ databases that return naive datetimes.
    """

    impl = DateTime
    cache_ok = True

    def __init__(self):
        super().__init__(timezone=True)

    def process_result_value(self, value, dialect):
        if value is not None and isinstance(value, datetime) and value.tzinfo is None:
            return value.replace(tzinfo=UTC)
        return value


Base = declarative_base()


server_squad_promo_groups = Table(
    'server_squad_promo_groups',
    Base.metadata,
    Column(
        'server_squad_id',
        Integer,
        ForeignKey('server_squads.id', ondelete='CASCADE'),
        primary_key=True,
    ),
    Column(
        'promo_group_id',
        Integer,
        ForeignKey('promo_groups.id', ondelete='CASCADE'),
        primary_key=True,
    ),
)


# M2M таблица для связи тарифов с промогруппами (доступ к тарифу)
tariff_promo_groups = Table(
    'tariff_promo_groups',
    Base.metadata,
    Column(
        'tariff_id',
        Integer,
        ForeignKey('tariffs.id', ondelete='CASCADE'),
        primary_key=True,
    ),
    Column(
        'promo_group_id',
        Integer,
        ForeignKey('promo_groups.id', ondelete='CASCADE'),
        primary_key=True,
    ),
)


# M2M таблица для связи платёжных методов с промогруппами (условия показа)
payment_method_promo_groups = Table(
    'payment_method_promo_groups',
    Base.metadata,
    Column(
        'payment_method_config_id',
        Integer,
        ForeignKey('payment_method_configs.id', ondelete='CASCADE'),
        primary_key=True,
    ),
    Column(
        'promo_group_id',
        Integer,
        ForeignKey('promo_groups.id', ondelete='CASCADE'),
        primary_key=True,
    ),
)


class UserStatus(Enum):
    ACTIVE = 'active'
    BLOCKED = 'blocked'
    DELETED = 'deleted'


class SubscriptionStatus(Enum):
    TRIAL = 'trial'
    ACTIVE = 'active'
    EXPIRED = 'expired'
    DISABLED = 'disabled'
    PENDING = 'pending'


class TransactionType(Enum):
    DEPOSIT = 'deposit'
    WITHDRAWAL = 'withdrawal'
    SUBSCRIPTION_PAYMENT = 'subscription_payment'
    REFUND = 'refund'
    REFERRAL_REWARD = 'referral_reward'
    POLL_REWARD = 'poll_reward'


class PromoCodeType(Enum):
    BALANCE = 'balance'
    SUBSCRIPTION_DAYS = 'subscription_days'
    TRIAL_SUBSCRIPTION = 'trial_subscription'
    PROMO_GROUP = 'promo_group'
    DISCOUNT = 'discount'  # Одноразовая процентная скидка (balance_bonus_kopeks = процент, subscription_days = часы)


class PaymentMethod(Enum):
    TELEGRAM_STARS = 'telegram_stars'
    TRIBUTE = 'tribute'
    YOOKASSA = 'yookassa'
    CRYPTOBOT = 'cryptobot'
    HELEKET = 'heleket'
    MULENPAY = 'mulenpay'
    PAL24 = 'pal24'
    WATA = 'wata'
    PLATEGA = 'platega'
    CLOUDPAYMENTS = 'cloudpayments'
    FREEKASSA = 'freekassa'
    KASSA_AI = 'kassa_ai'
    MANUAL = 'manual'
    BALANCE = 'balance'


class MainMenuButtonActionType(Enum):
    URL = 'url'
    MINI_APP = 'mini_app'


class MainMenuButtonVisibility(Enum):
    ALL = 'all'
    ADMINS = 'admins'
    SUBSCRIBERS = 'subscribers'


class WheelPrizeType(Enum):
    """Типы призов на колесе удачи."""

    SUBSCRIPTION_DAYS = 'subscription_days'
    BALANCE_BONUS = 'balance_bonus'
    TRAFFIC_GB = 'traffic_gb'
    PROMOCODE = 'promocode'
    NOTHING = 'nothing'


class WheelSpinPaymentType(Enum):
    """Способы оплаты спина колеса."""

    TELEGRAM_STARS = 'telegram_stars'
    SUBSCRIPTION_DAYS = 'subscription_days'


class YooKassaPayment(Base):
    __tablename__ = 'yookassa_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
    yookassa_payment_id = Column(String(255), unique=True, nullable=False, index=True)
    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(3), default='RUB', nullable=False)
    description = Column(Text, nullable=True)
    status = Column(String(50), nullable=False)
    is_paid = Column(Boolean, default=False)
    is_captured = Column(Boolean, default=False)
    confirmation_url = Column(Text, nullable=True)
    metadata_json = Column(JSON, nullable=True)
    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)
    payment_method_type = Column(String(50), nullable=True)
    refundable = Column(Boolean, default=False)
    test_mode = Column(Boolean, default=False)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())
    yookassa_created_at = Column(AwareDateTime(), nullable=True)
    captured_at = Column(AwareDateTime(), nullable=True)
    user = relationship('User', backref='yookassa_payments')
    transaction = relationship('Transaction', backref='yookassa_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    @property
    def is_pending(self) -> bool:
        return self.status == 'pending'

    @property
    def is_succeeded(self) -> bool:
        return self.status == 'succeeded' and self.is_paid

    @property
    def is_failed(self) -> bool:
        return self.status in ['canceled', 'failed']

    @property
    def can_be_captured(self) -> bool:
        return self.status == 'waiting_for_capture'

    def __repr__(self):
        return f'<YooKassaPayment(id={self.id}, yookassa_id={self.yookassa_payment_id}, amount={self.amount_rubles}₽, status={self.status})>'


class CryptoBotPayment(Base):
    __tablename__ = 'cryptobot_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    invoice_id = Column(String(255), unique=True, nullable=False, index=True)
    amount = Column(String(50), nullable=False)
    asset = Column(String(10), nullable=False)

    status = Column(String(50), nullable=False)
    description = Column(Text, nullable=True)
    payload = Column(Text, nullable=True)

    bot_invoice_url = Column(Text, nullable=True)
    mini_app_invoice_url = Column(Text, nullable=True)
    web_app_invoice_url = Column(Text, nullable=True)

    paid_at = Column(AwareDateTime(), nullable=True)
    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', backref='cryptobot_payments')
    transaction = relationship('Transaction', backref='cryptobot_payment')

    @property
    def amount_float(self) -> float:
        try:
            return float(self.amount)
        except (ValueError, TypeError):
            return 0.0

    @property
    def is_paid(self) -> bool:
        return self.status == 'paid'

    @property
    def is_pending(self) -> bool:
        return self.status == 'active'

    @property
    def is_expired(self) -> bool:
        return self.status == 'expired'

    def __repr__(self):
        return f'<CryptoBotPayment(id={self.id}, invoice_id={self.invoice_id}, amount={self.amount} {self.asset}, status={self.status})>'


class HeleketPayment(Base):
    __tablename__ = 'heleket_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    uuid = Column(String(255), unique=True, nullable=False, index=True)
    order_id = Column(String(128), unique=True, nullable=False, index=True)

    amount = Column(String(50), nullable=False)
    currency = Column(String(10), nullable=False)
    payer_amount = Column(String(50), nullable=True)
    payer_currency = Column(String(10), nullable=True)
    exchange_rate = Column(Float, nullable=True)
    discount_percent = Column(Integer, nullable=True)

    status = Column(String(50), nullable=False)
    payment_url = Column(Text, nullable=True)
    metadata_json = Column(JSON, nullable=True)

    paid_at = Column(AwareDateTime(), nullable=True)
    expires_at = Column(AwareDateTime(), nullable=True)
    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', backref='heleket_payments')
    transaction = relationship('Transaction', backref='heleket_payment')

    @property
    def amount_float(self) -> float:
        try:
            return float(self.amount)
        except (TypeError, ValueError):
            return 0.0

    @property
    def amount_kopeks(self) -> int:
        return int(round(self.amount_float * 100))

    @property
    def payer_amount_float(self) -> float:
        try:
            return float(self.payer_amount) if self.payer_amount is not None else 0.0
        except (TypeError, ValueError):
            return 0.0

    @property
    def is_paid(self) -> bool:
        return self.status in {'paid', 'paid_over'}

    def __repr__(self):
        return (
            f'<HeleketPayment(id={self.id}, uuid={self.uuid}, order_id={self.order_id}, amount={self.amount}'
            f' {self.currency}, status={self.status})>'
        )


class MulenPayPayment(Base):
    __tablename__ = 'mulenpay_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    mulen_payment_id = Column(Integer, nullable=True, index=True)
    uuid = Column(String(255), unique=True, nullable=False, index=True)
    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(10), nullable=False, default='RUB')
    description = Column(Text, nullable=True)

    status = Column(String(50), nullable=False, default='created')
    is_paid = Column(Boolean, default=False)
    paid_at = Column(AwareDateTime(), nullable=True)

    payment_url = Column(Text, nullable=True)
    metadata_json = Column(JSON, nullable=True)
    callback_payload = Column(JSON, nullable=True)

    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', backref='mulenpay_payments')
    transaction = relationship('Transaction', backref='mulenpay_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    def __repr__(self) -> str:  # pragma: no cover - debug helper
        return f'<MulenPayPayment(id={self.id}, mulen_id={self.mulen_payment_id}, amount={self.amount_rubles}₽, status={self.status})>'


class Pal24Payment(Base):
    __tablename__ = 'pal24_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    bill_id = Column(String(255), unique=True, nullable=False, index=True)
    order_id = Column(String(255), nullable=True, index=True)
    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(10), nullable=False, default='RUB')
    description = Column(Text, nullable=True)
    type = Column(String(20), nullable=False, default='normal')

    status = Column(String(50), nullable=False, default='NEW')
    is_active = Column(Boolean, default=True)
    is_paid = Column(Boolean, default=False)
    paid_at = Column(AwareDateTime(), nullable=True)
    last_status = Column(String(50), nullable=True)
    last_status_checked_at = Column(AwareDateTime(), nullable=True)

    link_url = Column(Text, nullable=True)
    link_page_url = Column(Text, nullable=True)
    metadata_json = Column(JSON, nullable=True)
    callback_payload = Column(JSON, nullable=True)

    payment_id = Column(String(255), nullable=True, index=True)
    payment_status = Column(String(50), nullable=True)
    payment_method = Column(String(50), nullable=True)
    balance_amount = Column(String(50), nullable=True)
    balance_currency = Column(String(10), nullable=True)
    payer_account = Column(String(255), nullable=True)

    ttl = Column(Integer, nullable=True)
    expires_at = Column(AwareDateTime(), nullable=True)

    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', backref='pal24_payments')
    transaction = relationship('Transaction', backref='pal24_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    @property
    def is_pending(self) -> bool:
        return self.status in {'NEW', 'PROCESS'}

    def __repr__(self) -> str:  # pragma: no cover - debug helper
        return (
            f'<Pal24Payment(id={self.id}, bill_id={self.bill_id}, amount={self.amount_rubles}₽, status={self.status})>'
        )


class WataPayment(Base):
    __tablename__ = 'wata_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    payment_link_id = Column(String(64), unique=True, nullable=False, index=True)
    order_id = Column(String(255), nullable=True, index=True)
    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(10), nullable=False, default='RUB')
    description = Column(Text, nullable=True)
    type = Column(String(50), nullable=True)

    status = Column(String(50), nullable=False, default='Opened')
    is_paid = Column(Boolean, default=False)
    paid_at = Column(AwareDateTime(), nullable=True)
    last_status = Column(String(50), nullable=True)
    terminal_public_id = Column(String(64), nullable=True)

    url = Column(Text, nullable=True)
    success_redirect_url = Column(Text, nullable=True)
    fail_redirect_url = Column(Text, nullable=True)
    metadata_json = Column(JSON, nullable=True)
    callback_payload = Column(JSON, nullable=True)

    expires_at = Column(AwareDateTime(), nullable=True)

    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', backref='wata_payments')
    transaction = relationship('Transaction', backref='wata_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    def __repr__(self) -> str:  # pragma: no cover - debug helper
        return f'<WataPayment(id={self.id}, link_id={self.payment_link_id}, amount={self.amount_rubles}₽, status={self.status})>'


class PlategaPayment(Base):
    __tablename__ = 'platega_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    platega_transaction_id = Column(String(255), unique=True, nullable=True, index=True)
    correlation_id = Column(String(64), unique=True, nullable=False, index=True)
    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(10), nullable=False, default='RUB')
    description = Column(Text, nullable=True)

    payment_method_code = Column(Integer, nullable=False)
    status = Column(String(50), nullable=False, default='PENDING')
    is_paid = Column(Boolean, default=False)
    paid_at = Column(AwareDateTime(), nullable=True)

    redirect_url = Column(Text, nullable=True)
    return_url = Column(Text, nullable=True)
    failed_url = Column(Text, nullable=True)
    payload = Column(String(255), nullable=True)
    metadata_json = Column(JSON, nullable=True)
    callback_payload = Column(JSON, nullable=True)

    expires_at = Column(AwareDateTime(), nullable=True)

    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', backref='platega_payments')
    transaction = relationship('Transaction', backref='platega_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    def __repr__(self) -> str:  # pragma: no cover - debug helper
        return f'<PlategaPayment(id={self.id}, transaction_id={self.platega_transaction_id}, amount={self.amount_rubles}₽, status={self.status}, method={self.payment_method_code})>'


class CloudPaymentsPayment(Base):
    __tablename__ = 'cloudpayments_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    # CloudPayments идентификаторы
    transaction_id_cp = Column(BigInteger, unique=True, nullable=True, index=True)  # TransactionId от CloudPayments
    invoice_id = Column(String(255), unique=True, nullable=False, index=True)  # Наш InvoiceId

    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(10), nullable=False, default='RUB')
    description = Column(Text, nullable=True)

    status = Column(String(50), nullable=False, default='pending')  # pending, completed, failed, authorized
    is_paid = Column(Boolean, default=False)
    paid_at = Column(AwareDateTime(), nullable=True)

    # Данные карты (маскированные)
    card_first_six = Column(String(6), nullable=True)
    card_last_four = Column(String(4), nullable=True)
    card_type = Column(String(50), nullable=True)  # Visa, MasterCard, etc.
    card_exp_date = Column(String(10), nullable=True)  # MM/YY

    # Токен для рекуррентных платежей
    token = Column(String(255), nullable=True)

    # URL для оплаты (виджет)
    payment_url = Column(Text, nullable=True)

    # Email плательщика
    email = Column(String(255), nullable=True)

    # Тестовый режим
    test_mode = Column(Boolean, default=False)

    # Дополнительные данные
    metadata_json = Column(JSON, nullable=True)
    callback_payload = Column(JSON, nullable=True)

    # Связь с транзакцией в нашей системе
    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', backref='cloudpayments_payments')
    transaction = relationship('Transaction', backref='cloudpayments_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    @property
    def is_pending(self) -> bool:
        return self.status == 'pending'

    @property
    def is_completed(self) -> bool:
        return self.status == 'completed' and self.is_paid

    @property
    def is_failed(self) -> bool:
        return self.status == 'failed'

    def __repr__(self) -> str:  # pragma: no cover - debug helper
        return f'<CloudPaymentsPayment(id={self.id}, invoice={self.invoice_id}, amount={self.amount_rubles}₽, status={self.status})>'


class FreekassaPayment(Base):
    __tablename__ = 'freekassa_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    # Идентификаторы
    order_id = Column(String(64), unique=True, nullable=False, index=True)  # Наш ID заказа
    freekassa_order_id = Column(String(64), unique=True, nullable=True, index=True)  # intid от Freekassa

    # Суммы
    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(10), nullable=False, default='RUB')
    description = Column(Text, nullable=True)

    # Статусы
    status = Column(String(32), nullable=False, default='pending')  # pending, success, failed, expired
    is_paid = Column(Boolean, default=False)

    # Данные платежа
    payment_url = Column(Text, nullable=True)
    payment_system_id = Column(Integer, nullable=True)  # ID платежной системы FK

    # Метаданные
    metadata_json = Column(JSON, nullable=True)
    callback_payload = Column(JSON, nullable=True)

    # Временные метки
    paid_at = Column(AwareDateTime(), nullable=True)
    expires_at = Column(AwareDateTime(), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    # Связь с транзакцией
    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    # Relationships
    user = relationship('User', backref='freekassa_payments')
    transaction = relationship('Transaction', backref='freekassa_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    @property
    def is_pending(self) -> bool:
        return self.status == 'pending'

    @property
    def is_success(self) -> bool:
        return self.status == 'success' and self.is_paid

    @property
    def is_failed(self) -> bool:
        return self.status in ['failed', 'expired']

    def __repr__(self) -> str:  # pragma: no cover - debug helper
        return f'<FreekassaPayment(id={self.id}, order_id={self.order_id}, amount={self.amount_rubles}₽, status={self.status})>'


class KassaAiPayment(Base):
    """Платежи через KassaAI (api.fk.life)."""

    __tablename__ = 'kassa_ai_payments'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    # Идентификаторы
    order_id = Column(String(64), unique=True, nullable=False, index=True)  # Наш ID заказа
    kassa_ai_order_id = Column(String(64), unique=True, nullable=True, index=True)  # orderId от KassaAI

    # Суммы
    amount_kopeks = Column(Integer, nullable=False)
    currency = Column(String(10), nullable=False, default='RUB')
    description = Column(Text, nullable=True)

    # Статусы
    status = Column(String(32), nullable=False, default='pending')  # pending, success, failed, expired
    is_paid = Column(Boolean, default=False)

    # Данные платежа
    payment_url = Column(Text, nullable=True)
    payment_system_id = Column(Integer, nullable=True)  # ID платежной системы (44=СБП, 36=Карты, 43=SberPay)

    # Метаданные
    metadata_json = Column(JSON, nullable=True)
    callback_payload = Column(JSON, nullable=True)

    # Временные метки
    paid_at = Column(AwareDateTime(), nullable=True)
    expires_at = Column(AwareDateTime(), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    # Связь с транзакцией
    transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)

    # Relationships
    user = relationship('User', backref='kassa_ai_payments')
    transaction = relationship('Transaction', backref='kassa_ai_payment')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100

    @property
    def is_pending(self) -> bool:
        return self.status == 'pending'

    @property
    def is_success(self) -> bool:
        return self.status == 'success' and self.is_paid

    @property
    def is_failed(self) -> bool:
        return self.status in ['failed', 'expired']

    def __repr__(self) -> str:  # pragma: no cover - debug helper
        return f'<KassaAiPayment(id={self.id}, order_id={self.order_id}, amount={self.amount_rubles}₽, status={self.status})>'


class PromoGroup(Base):
    __tablename__ = 'promo_groups'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(255), unique=True, nullable=False)
    priority = Column(Integer, nullable=False, default=0, index=True)
    server_discount_percent = Column(Integer, nullable=False, default=0)
    traffic_discount_percent = Column(Integer, nullable=False, default=0)
    device_discount_percent = Column(Integer, nullable=False, default=0)
    period_discounts = Column(JSON, nullable=True, default=dict)
    auto_assign_total_spent_kopeks = Column(Integer, nullable=True, default=None)
    apply_discounts_to_addons = Column(Boolean, nullable=False, default=True)
    is_default = Column(Boolean, nullable=False, default=False)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    users = relationship('User', back_populates='promo_group')
    user_promo_groups = relationship('UserPromoGroup', back_populates='promo_group', cascade='all, delete-orphan')
    server_squads = relationship(
        'ServerSquad',
        secondary=server_squad_promo_groups,
        back_populates='allowed_promo_groups',
        lazy='selectin',
    )

    def _get_period_discounts_map(self) -> dict[int, int]:
        raw_discounts = self.period_discounts or {}

        if isinstance(raw_discounts, dict):
            items = raw_discounts.items()
        else:
            items = []

        normalized: dict[int, int] = {}

        for key, value in items:
            try:
                period = int(key)
                percent = int(value)
            except (TypeError, ValueError):
                continue

            normalized[period] = max(0, min(100, percent))

        return normalized

    def _get_period_discount(self, period_days: int | None) -> int:
        if not period_days:
            return 0

        discounts = self._get_period_discounts_map()

        if period_days in discounts:
            return discounts[period_days]

        if self.is_default:
            try:
                from app.config import settings

                if settings.is_base_promo_group_period_discount_enabled():
                    config_discounts = settings.get_base_promo_group_period_discounts()
                    return config_discounts.get(period_days, 0)
            except Exception:
                return 0

        return 0

    def get_discount_percent(self, category: str, period_days: int | None = None) -> int:
        if category == 'period':
            return max(0, min(100, self._get_period_discount(period_days)))

        mapping = {
            'servers': self.server_discount_percent,
            'traffic': self.traffic_discount_percent,
            'devices': self.device_discount_percent,
        }
        percent = mapping.get(category) or 0

        if percent == 0 and self.is_default:
            base_period_discount = self._get_period_discount(period_days)
            percent = max(percent, base_period_discount)

        return max(0, min(100, percent))


class UserPromoGroup(Base):
    """Таблица связи Many-to-Many между пользователями и промогруппами."""

    __tablename__ = 'user_promo_groups'

    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), primary_key=True)
    promo_group_id = Column(Integer, ForeignKey('promo_groups.id', ondelete='CASCADE'), primary_key=True)
    assigned_at = Column(AwareDateTime(), default=func.now())
    assigned_by = Column(String(50), default='system')

    user = relationship('User', back_populates='user_promo_groups')
    promo_group = relationship('PromoGroup', back_populates='user_promo_groups')

    def __repr__(self):
        return f"<UserPromoGroup(user_id={self.user_id}, promo_group_id={self.promo_group_id}, assigned_by='{self.assigned_by}')>"


class Tariff(Base):
    """Тарифный план для режима продаж 'Тарифы'."""

    __tablename__ = 'tariffs'

    id = Column(Integer, primary_key=True, index=True)

    # Основная информация
    name = Column(String(255), nullable=False)
    description = Column(Text, nullable=True)
    display_order = Column(Integer, default=0, nullable=False)
    is_active = Column(Boolean, default=True, nullable=False)

    # Параметры тарифа
    traffic_limit_gb = Column(Integer, nullable=False, default=100)  # 0 = безлимит
    device_limit = Column(Integer, nullable=False, default=1)
    device_price_kopeks = Column(
        Integer, nullable=True, default=None
    )  # Цена за доп. устройство (None = нельзя докупить)
    max_device_limit = Column(Integer, nullable=True, default=None)  # Макс. устройств (None = без ограничений)

    # Сквады (серверы) доступные в тарифе
    allowed_squads = Column(JSON, default=list)  # список UUID сквадов

    # Лимиты трафика по серверам (JSON: {"uuid": {"traffic_limit_gb": 100}, ...})
    # Если сервер не указан - используется общий traffic_limit_gb
    server_traffic_limits = Column(JSON, default=dict)

    # Цены на периоды в копейках (JSON: {"14": 30000, "30": 50000, "90": 120000, ...})
    period_prices = Column(JSON, nullable=False, default=dict)

    # Уровень тарифа (для визуального отображения, 1 = базовый)
    tier_level = Column(Integer, default=1, nullable=False)

    # Дополнительные настройки
    is_trial_available = Column(Boolean, default=False, nullable=False)  # Можно ли взять триал на этом тарифе
    allow_traffic_topup = Column(Boolean, default=True, nullable=False)  # Разрешена ли докупка трафика для этого тарифа

    # Докупка трафика
    traffic_topup_enabled = Column(Boolean, default=False, nullable=False)  # Разрешена ли докупка трафика
    # Пакеты трафика: JSON {"5": 5000, "10": 9000, "20": 15000} (ГБ: цена в копейках)
    traffic_topup_packages = Column(JSON, default=dict)
    # Максимальный лимит трафика после докупки (0 = без ограничений)
    max_topup_traffic_gb = Column(Integer, default=0, nullable=False)

    # Суточный тариф - ежедневное списание
    is_daily = Column(Boolean, default=False, nullable=False)  # Является ли тариф суточным
    daily_price_kopeks = Column(Integer, default=0, nullable=False)  # Цена за день в копейках

    # Произвольное количество дней
    custom_days_enabled = Column(Boolean, default=False, nullable=False)  # Разрешить произвольное кол-во дней
    price_per_day_kopeks = Column(Integer, default=0, nullable=False)  # Цена за 1 день в копейках
    min_days = Column(Integer, default=1, nullable=False)  # Минимальное количество дней
    max_days = Column(Integer, default=365, nullable=False)  # Максимальное количество дней

    # Произвольный трафик при покупке
    custom_traffic_enabled = Column(Boolean, default=False, nullable=False)  # Разрешить произвольный трафик
    traffic_price_per_gb_kopeks = Column(Integer, default=0, nullable=False)  # Цена за 1 ГБ в копейках
    min_traffic_gb = Column(Integer, default=1, nullable=False)  # Минимальный трафик в ГБ
    max_traffic_gb = Column(Integer, default=1000, nullable=False)  # Максимальный трафик в ГБ

    # Режим сброса трафика: DAY, WEEK, MONTH, NO_RESET (по умолчанию берётся из конфига)
    traffic_reset_mode = Column(String(20), nullable=True, default=None)  # None = использовать глобальную настройку

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    # M2M связь с промогруппами (какие промогруппы имеют доступ к тарифу)
    allowed_promo_groups = relationship(
        'PromoGroup',
        secondary=tariff_promo_groups,
        lazy='selectin',
    )

    # Подписки на этом тарифе
    subscriptions = relationship('Subscription', back_populates='tariff')

    @property
    def is_unlimited_traffic(self) -> bool:
        """Проверяет, безлимитный ли трафик."""
        return self.traffic_limit_gb == 0

    def get_price_for_period(self, period_days: int) -> int | None:
        """Возвращает цену в копейках для указанного периода."""
        prices = self.period_prices or {}
        return prices.get(str(period_days))

    def get_available_periods(self) -> list[int]:
        """Возвращает список доступных периодов в днях."""
        prices = self.period_prices or {}
        return sorted([int(p) for p in prices.keys()])

    def get_price_rubles(self, period_days: int) -> float | None:
        """Возвращает цену в рублях для указанного периода."""
        price_kopeks = self.get_price_for_period(period_days)
        if price_kopeks is not None:
            return price_kopeks / 100
        return None

    def get_traffic_limit_for_server(self, squad_uuid: str) -> int:
        """Возвращает лимит трафика для конкретного сервера.

        Если для сервера настроен отдельный лимит - возвращает его,
        иначе возвращает общий traffic_limit_gb тарифа.
        """
        limits = self.server_traffic_limits or {}
        if squad_uuid in limits:
            server_limit = limits[squad_uuid]
            if isinstance(server_limit, dict) and 'traffic_limit_gb' in server_limit:
                return server_limit['traffic_limit_gb']
            if isinstance(server_limit, int):
                return server_limit
        return self.traffic_limit_gb

    def is_available_for_promo_group(self, promo_group_id: int | None) -> bool:
        """Проверяет, доступен ли тариф для указанной промогруппы."""
        if not self.allowed_promo_groups:
            return True  # Если нет ограничений - доступен всем
        if promo_group_id is None:
            return True  # Если у пользователя нет группы - доступен
        return any(pg.id == promo_group_id for pg in self.allowed_promo_groups)

    def get_traffic_topup_packages(self) -> dict[int, int]:
        """Возвращает пакеты трафика для докупки: {ГБ: цена в копейках}."""
        packages = self.traffic_topup_packages or {}
        return {int(gb): int(price) for gb, price in packages.items()}

    def get_traffic_topup_price(self, gb: int) -> int | None:
        """Возвращает цену в копейках для указанного пакета трафика."""
        packages = self.get_traffic_topup_packages()
        return packages.get(gb)

    def get_available_traffic_packages(self) -> list[int]:
        """Возвращает список доступных пакетов трафика в ГБ."""
        packages = self.get_traffic_topup_packages()
        return sorted(packages.keys())

    def can_topup_traffic(self) -> bool:
        """Проверяет, можно ли докупить трафик на этом тарифе."""
        return self.traffic_topup_enabled and bool(self.traffic_topup_packages) and not self.is_unlimited_traffic

    def get_daily_price_rubles(self) -> float:
        """Возвращает суточную цену в рублях."""
        return self.daily_price_kopeks / 100 if self.daily_price_kopeks else 0

    def get_price_for_custom_days(self, days: int) -> int | None:
        """Возвращает цену для произвольного количества дней."""
        if not self.custom_days_enabled or not self.price_per_day_kopeks:
            return None
        if days < self.min_days or days > self.max_days:
            return None
        return self.price_per_day_kopeks * days

    def get_price_for_custom_traffic(self, gb: int) -> int | None:
        """Возвращает цену для произвольного количества трафика."""
        if not self.custom_traffic_enabled or not self.traffic_price_per_gb_kopeks:
            return None
        if gb < self.min_traffic_gb or gb > self.max_traffic_gb:
            return None
        return self.traffic_price_per_gb_kopeks * gb

    def can_purchase_custom_days(self) -> bool:
        """Проверяет, можно ли купить произвольное количество дней."""
        return self.custom_days_enabled and self.price_per_day_kopeks > 0

    def can_purchase_custom_traffic(self) -> bool:
        """Проверяет, можно ли купить произвольный трафик."""
        return self.custom_traffic_enabled and self.traffic_price_per_gb_kopeks > 0

    def __repr__(self):
        return f"<Tariff(id={self.id}, name='{self.name}', tier={self.tier_level}, active={self.is_active})>"


class PartnerStatus(Enum):
    """Статусы партнёрского аккаунта."""

    NONE = 'none'  # Не подавал заявку
    PENDING = 'pending'  # Заявка на рассмотрении
    APPROVED = 'approved'  # Партнёр одобрен
    REJECTED = 'rejected'  # Заявка отклонена


class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True, index=True)
    telegram_id = Column(BigInteger, unique=True, index=True, nullable=True)  # Nullable для email-only пользователей
    auth_type = Column(String(20), default='telegram', nullable=False)  # "telegram" или "email"
    username = Column(String(255), nullable=True)
    first_name = Column(String(255), nullable=True)
    last_name = Column(String(255), nullable=True)
    status = Column(String(20), default=UserStatus.ACTIVE.value)
    language = Column(String(5), default='ru')
    balance_kopeks = Column(Integer, default=0)
    used_promocodes = Column(Integer, default=0)
    has_had_paid_subscription = Column(Boolean, default=False, nullable=False)
    referred_by_id = Column(Integer, ForeignKey('users.id'), nullable=True, index=True)
    referral_code = Column(String(20), unique=True, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())
    last_activity = Column(AwareDateTime(), default=func.now())
    remnawave_uuid = Column(String(255), nullable=True, unique=True)

    # Cabinet authentication fields
    email = Column(String(255), unique=True, nullable=True, index=True)
    email_verified = Column(Boolean, default=False, nullable=False)
    email_verified_at = Column(AwareDateTime(), nullable=True)
    password_hash = Column(String(255), nullable=True)
    email_verification_token = Column(String(255), nullable=True)
    email_verification_expires = Column(AwareDateTime(), nullable=True)
    password_reset_token = Column(String(255), nullable=True)
    password_reset_expires = Column(AwareDateTime(), nullable=True)
    cabinet_last_login = Column(AwareDateTime(), nullable=True)
    # Email change fields
    email_change_new = Column(String(255), nullable=True)  # New email pending verification
    email_change_code = Column(String(6), nullable=True)  # 6-digit verification code
    email_change_expires = Column(AwareDateTime(), nullable=True)  # Code expiration
    # OAuth provider IDs
    google_id = Column(String(255), unique=True, nullable=True, index=True)
    yandex_id = Column(String(255), unique=True, nullable=True, index=True)
    discord_id = Column(String(255), unique=True, nullable=True, index=True)
    vk_id = Column(BigInteger, unique=True, nullable=True, index=True)
    broadcasts = relationship('BroadcastHistory', back_populates='admin')
    referrals = relationship('User', backref='referrer', remote_side=[id], foreign_keys='User.referred_by_id')
    subscription = relationship('Subscription', back_populates='user', uselist=False)
    transactions = relationship('Transaction', back_populates='user')
    referral_earnings = relationship('ReferralEarning', foreign_keys='ReferralEarning.user_id', back_populates='user')
    discount_offers = relationship('DiscountOffer', back_populates='user')
    promo_offer_logs = relationship('PromoOfferLog', back_populates='user')
    lifetime_used_traffic_bytes = Column(BigInteger, default=0)
    auto_promo_group_assigned = Column(Boolean, nullable=False, default=False)
    auto_promo_group_threshold_kopeks = Column(BigInteger, nullable=False, default=0)
    referral_commission_percent = Column(Integer, nullable=True)
    promo_offer_discount_percent = Column(Integer, nullable=False, default=0)
    promo_offer_discount_source = Column(String(100), nullable=True)
    promo_offer_discount_expires_at = Column(AwareDateTime(), nullable=True)
    last_remnawave_sync = Column(AwareDateTime(), nullable=True)
    trojan_password = Column(String(255), nullable=True)
    vless_uuid = Column(String(255), nullable=True)
    ss_password = Column(String(255), nullable=True)
    has_made_first_topup: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    promo_group_id = Column(Integer, ForeignKey('promo_groups.id', ondelete='RESTRICT'), nullable=True, index=True)
    promo_group = relationship('PromoGroup', back_populates='users')
    user_promo_groups = relationship('UserPromoGroup', back_populates='user', cascade='all, delete-orphan')
    poll_responses = relationship('PollResponse', back_populates='user')
    admin_roles_rel = relationship('UserRole', foreign_keys='[UserRole.user_id]', back_populates='user')
    notification_settings = Column(JSON, nullable=True, default=dict)
    last_pinned_message_id = Column(Integer, nullable=True)

    # Ограничения пользователя
    restriction_topup = Column(Boolean, default=False, nullable=False)  # Запрет пополнения
    restriction_subscription = Column(Boolean, default=False, nullable=False)  # Запрет продления/покупки
    restriction_reason = Column(String(500), nullable=True)  # Причина ограничения

    # Партнёрская система
    partner_status = Column(String(20), default=PartnerStatus.NONE.value, nullable=False, index=True)

    @property
    def is_partner(self) -> bool:
        """Проверить, является ли пользователь одобренным партнёром."""
        return self.partner_status == PartnerStatus.APPROVED.value

    @property
    def has_restrictions(self) -> bool:
        """Проверить, есть ли у пользователя активные ограничения."""
        return self.restriction_topup or self.restriction_subscription

    @property
    def balance_rubles(self) -> float:
        return self.balance_kopeks / 100

    @property
    def full_name(self) -> str:
        """Полное имя пользователя с поддержкой email-only юзеров."""
        parts = [self.first_name, self.last_name]
        name = ' '.join(filter(None, parts))
        if name:
            return name
        if self.username:
            return self.username
        if self.telegram_id:
            return f'ID{self.telegram_id}'
        if self.email:
            return self.email.split('@')[0]
        return f'User{self.id}'

    @property
    def is_email_user(self) -> bool:
        """Пользователь зарегистрирован через email (без Telegram)."""
        return self.auth_type == 'email' and self.telegram_id is None

    @property
    def is_web_user(self) -> bool:
        """Пользователь без Telegram (email, OAuth и т.д.)."""
        return self.telegram_id is None

    def get_primary_promo_group(self):
        """Возвращает промогруппу с максимальным приоритетом."""
        if not self.user_promo_groups:
            return getattr(self, 'promo_group', None)

        try:
            # Сортируем по приоритету группы (убывание), затем по ID группы
            # Используем getattr для защиты от ленивой загрузки
            sorted_groups = sorted(
                self.user_promo_groups,
                key=lambda upg: (getattr(upg.promo_group, 'priority', 0) if upg.promo_group else 0, upg.promo_group_id),
                reverse=True,
            )

            if sorted_groups and sorted_groups[0].promo_group:
                return sorted_groups[0].promo_group
        except Exception:
            # Если возникла ошибка (например, ленивая загрузка), fallback на старую связь
            pass

        # Fallback на старую связь если новая пустая или возникла ошибка
        return getattr(self, 'promo_group', None)

    def get_promo_discount(self, category: str, period_days: int | None = None) -> int:
        primary_group = self.get_primary_promo_group()
        if not primary_group:
            return 0
        return primary_group.get_discount_percent(category, period_days)

    def add_balance(self, kopeks: int) -> None:
        self.balance_kopeks += kopeks

    def subtract_balance(self, kopeks: int) -> bool:
        if self.balance_kopeks >= kopeks:
            self.balance_kopeks -= kopeks
            return True
        return False


class Subscription(Base):
    __tablename__ = 'subscriptions'
    __table_args__ = (
        Index('ix_subscriptions_status_trial', 'status', 'is_trial'),
        Index('ix_subscriptions_trial_created', 'is_trial', 'created_at'),
    )

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False, unique=True)

    status = Column(String(20), default=SubscriptionStatus.TRIAL.value)
    is_trial = Column(Boolean, default=True)

    start_date = Column(AwareDateTime(), default=func.now())
    end_date = Column(AwareDateTime(), nullable=False)

    traffic_limit_gb = Column(Integer, default=0)
    traffic_used_gb = Column(Float, default=0.0)
    purchased_traffic_gb = Column(Integer, default=0)  # Докупленный трафик
    traffic_reset_at = Column(
        AwareDateTime(), nullable=True
    )  # Дата сброса докупленного трафика (30 дней после первой докупки)

    subscription_url = Column(String, nullable=True)
    subscription_crypto_link = Column(String, nullable=True)

    device_limit = Column(Integer, default=1)
    modem_enabled = Column(Boolean, default=False)

    connected_squads = Column(JSON, default=list)

    autopay_enabled = Column(Boolean, default=False)
    autopay_days_before = Column(Integer, default=3)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    last_webhook_update_at = Column(AwareDateTime(), nullable=True)

    remnawave_short_uuid = Column(String(255), nullable=True)

    # Тариф (для режима продаж "Тарифы")
    tariff_id = Column(Integer, ForeignKey('tariffs.id', ondelete='SET NULL'), nullable=True, index=True)

    # Суточная подписка
    is_daily_paused = Column(
        Boolean, default=False, nullable=False
    )  # Приостановлена ли суточная подписка пользователем
    last_daily_charge_at = Column(AwareDateTime(), nullable=True)  # Время последнего суточного списания

    user = relationship('User', back_populates='subscription')
    tariff = relationship('Tariff', back_populates='subscriptions')
    discount_offers = relationship('DiscountOffer', back_populates='subscription')
    temporary_accesses = relationship(
        'SubscriptionTemporaryAccess', back_populates='subscription', passive_deletes=True
    )
    traffic_purchases = relationship(
        'TrafficPurchase', back_populates='subscription', passive_deletes=True, cascade='all, delete-orphan'
    )

    @property
    def is_active(self) -> bool:
        current_time = datetime.now(UTC)
        end = _aware(self.end_date)
        return self.status == SubscriptionStatus.ACTIVE.value and end is not None and end > current_time

    @property
    def is_expired(self) -> bool:
        """Проверяет, истёк ли срок подписки"""
        end = _aware(self.end_date)
        return end is not None and end <= datetime.now(UTC)

    @property
    def should_be_expired(self) -> bool:
        current_time = datetime.now(UTC)
        end = _aware(self.end_date)
        return self.status == SubscriptionStatus.ACTIVE.value and end is not None and end <= current_time

    @property
    def actual_status(self) -> str:
        current_time = datetime.now(UTC)
        end = _aware(self.end_date)

        if self.status == SubscriptionStatus.EXPIRED.value:
            return 'expired'

        if self.status == SubscriptionStatus.DISABLED.value:
            return 'disabled'

        if self.status == SubscriptionStatus.ACTIVE.value:
            if end is None or end <= current_time:
                return 'expired'
            return 'active'

        if self.status == SubscriptionStatus.TRIAL.value:
            if end is None or end <= current_time:
                return 'expired'
            return 'trial'

        return self.status

    @property
    def status_display(self) -> str:
        actual_status = self.actual_status

        if actual_status == 'expired':
            return '🔴 Истекла'
        if actual_status == 'active':
            if self.is_trial:
                return '🎯 Тестовая'
            return '🟢 Активна'
        if actual_status == 'disabled':
            return '⚫ Отключена'
        if actual_status == 'trial':
            return '🎯 Тестовая'

        return '❓ Неизвестно'

    @property
    def status_emoji(self) -> str:
        actual_status = self.actual_status

        if actual_status == 'expired':
            return '🔴'
        if actual_status == 'active':
            if self.is_trial:
                return '🎁'
            return '💎'
        if actual_status == 'disabled':
            return '⚫'
        if actual_status == 'trial':
            return '🎁'

        return '❓'

    @property
    def days_left(self) -> int:
        end = _aware(self.end_date)
        if end is None:
            return 0
        current_time = datetime.now(UTC)
        if end <= current_time:
            return 0
        delta = end - current_time
        return max(0, delta.days)

    @property
    def time_left_display(self) -> str:
        end = _aware(self.end_date)
        current_time = datetime.now(UTC)
        if end is None or end <= current_time:
            return 'истёк'

        delta = end - current_time
        days = delta.days
        hours = delta.seconds // 3600
        minutes = (delta.seconds % 3600) // 60

        if days > 0:
            return f'{days} дн.'
        if hours > 0:
            return f'{hours} ч.'
        return f'{minutes} мин.'

    @property
    def traffic_used_percent(self) -> float:
        if not self.traffic_limit_gb:
            return 0.0
        used = self.traffic_used_gb or 0.0
        return min((used / self.traffic_limit_gb) * 100, 100.0)

    def extend_subscription(self, days: int):
        end = _aware(self.end_date)
        if end and end > datetime.now(UTC):
            self.end_date = end + timedelta(days=days)
        else:
            self.end_date = datetime.now(UTC) + timedelta(days=days)

        if self.status == SubscriptionStatus.EXPIRED.value:
            self.status = SubscriptionStatus.ACTIVE.value

    def add_traffic(self, gb: int):
        if self.traffic_limit_gb == 0:
            return
        self.traffic_limit_gb += gb

    @property
    def is_daily_tariff(self) -> bool:
        """Проверяет, является ли тариф подписки суточным."""
        if self.tariff:
            return getattr(self.tariff, 'is_daily', False)
        return False

    @property
    def daily_price_kopeks(self) -> int:
        """Возвращает суточную цену тарифа в копейках."""
        if self.tariff:
            return getattr(self.tariff, 'daily_price_kopeks', 0)
        return 0

    @property
    def can_charge_daily(self) -> bool:
        """Проверяет, можно ли списать суточную оплату."""
        if not self.is_daily_tariff:
            return False
        if self.is_daily_paused:
            return False
        if self.status != SubscriptionStatus.ACTIVE.value:
            return False
        return True


class TrafficPurchase(Base):
    """Докупка трафика с индивидуальной датой истечения."""

    __tablename__ = 'traffic_purchases'
    __table_args__ = (Index('ix_traffic_purchases_created_at', 'created_at'),)

    id = Column(Integer, primary_key=True, index=True)
    subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='CASCADE'), nullable=False, index=True)

    traffic_gb = Column(Integer, nullable=False)  # Количество ГБ в покупке
    expires_at = Column(AwareDateTime(), nullable=False, index=True)  # Дата истечения (покупка + 30 дней)

    created_at = Column(AwareDateTime(), default=func.now())

    subscription = relationship('Subscription', back_populates='traffic_purchases')

    @property
    def is_expired(self) -> bool:
        """Проверяет, истекла ли докупка."""
        return datetime.now(UTC) >= _aware(self.expires_at)


class Transaction(Base):
    __tablename__ = 'transactions'
    __table_args__ = (
        Index('ix_transactions_type_created_completed', 'type', 'created_at', 'is_completed'),
        Index('ix_transactions_user_created', 'user_id', 'created_at'),
        Index('ix_transactions_type_method_created', 'type', 'payment_method', 'created_at'),
    )

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    type = Column(String(50), nullable=False)
    amount_kopeks = Column(Integer, nullable=False)
    description = Column(Text, nullable=True)

    payment_method = Column(String(50), nullable=True)
    external_id = Column(String(255), nullable=True)

    is_completed = Column(Boolean, default=True)

    # NaloGO чек
    receipt_uuid = Column(String(255), nullable=True, index=True)
    receipt_created_at = Column(AwareDateTime(), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    completed_at = Column(AwareDateTime(), nullable=True)

    user = relationship('User', back_populates='transactions')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100


class SubscriptionConversion(Base):
    __tablename__ = 'subscription_conversions'
    __table_args__ = (
        Index('ix_sub_conversions_converted_at', 'converted_at'),
        Index('ix_sub_conversions_user_id', 'user_id'),
    )

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    converted_at = Column(AwareDateTime(), default=func.now())

    trial_duration_days = Column(Integer, nullable=True)

    payment_method = Column(String(50), nullable=True)

    first_payment_amount_kopeks = Column(Integer, nullable=True)

    first_paid_period_days = Column(Integer, nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())

    user = relationship('User', backref='subscription_conversions')

    @property
    def first_payment_amount_rubles(self) -> float:
        return (self.first_payment_amount_kopeks or 0) / 100

    def __repr__(self):
        return f'<SubscriptionConversion(user_id={self.user_id}, converted_at={self.converted_at})>'


class PromoCode(Base):
    __tablename__ = 'promocodes'

    id = Column(Integer, primary_key=True, index=True)

    code = Column(String(50), unique=True, nullable=False, index=True)
    type = Column(String(50), nullable=False)

    balance_bonus_kopeks = Column(Integer, default=0)
    subscription_days = Column(Integer, default=0)

    max_uses = Column(Integer, default=1)
    current_uses = Column(Integer, default=0)

    valid_from = Column(AwareDateTime(), default=func.now())
    valid_until = Column(AwareDateTime(), nullable=True)

    is_active = Column(Boolean, default=True)
    first_purchase_only = Column(Boolean, default=False)  # Только для первой покупки

    created_by = Column(Integer, ForeignKey('users.id'), nullable=True)
    promo_group_id = Column(Integer, ForeignKey('promo_groups.id', ondelete='SET NULL'), nullable=True, index=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    uses = relationship('PromoCodeUse', back_populates='promocode')
    promo_group = relationship('PromoGroup')

    @property
    def is_valid(self) -> bool:
        now = datetime.now(UTC)
        return (
            self.is_active
            and self.current_uses < self.max_uses
            and _aware(self.valid_from) <= now
            and (self.valid_until is None or _aware(self.valid_until) >= now)
        )

    @property
    def uses_left(self) -> int:
        return max(0, self.max_uses - self.current_uses)


class PromoCodeUse(Base):
    __tablename__ = 'promocode_uses'

    id = Column(Integer, primary_key=True, index=True)
    promocode_id = Column(Integer, ForeignKey('promocodes.id'), nullable=False)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

    used_at = Column(AwareDateTime(), default=func.now())

    promocode = relationship('PromoCode', back_populates='uses')
    user = relationship('User')


class ReferralEarning(Base):
    __tablename__ = 'referral_earnings'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True)
    referral_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True)

    amount_kopeks = Column(Integer, nullable=False)
    reason = Column(String(100), nullable=False)

    referral_transaction_id = Column(Integer, ForeignKey('transactions.id'), nullable=True)
    campaign_id = Column(
        Integer, ForeignKey('advertising_campaigns.id', ondelete='SET NULL'), nullable=True, index=True
    )

    created_at = Column(AwareDateTime(), default=func.now())

    user = relationship('User', foreign_keys=[user_id], back_populates='referral_earnings')
    referral = relationship('User', foreign_keys=[referral_id])
    referral_transaction = relationship('Transaction')
    campaign = relationship('AdvertisingCampaign')

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100


class WithdrawalRequestStatus(Enum):
    """Статусы заявки на вывод реферального баланса."""

    PENDING = 'pending'  # Ожидает рассмотрения
    APPROVED = 'approved'  # Одобрена
    REJECTED = 'rejected'  # Отклонена
    COMPLETED = 'completed'  # Выполнена (деньги переведены)
    CANCELLED = 'cancelled'  # Отменена пользователем


class WithdrawalRequest(Base):
    """Заявка на вывод реферального баланса."""

    __tablename__ = 'withdrawal_requests'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True)

    amount_kopeks = Column(Integer, nullable=False)  # Сумма к выводу
    status = Column(String(50), default=WithdrawalRequestStatus.PENDING.value, nullable=False, index=True)

    # Данные для вывода (заполняет пользователь)
    payment_details = Column(Text, nullable=True)  # Реквизиты для перевода

    # Анализ на отмывание
    risk_score = Column(Integer, default=0)  # 0-100, чем выше — тем подозрительнее
    risk_analysis = Column(Text, nullable=True)  # JSON с деталями анализа

    # Обработка админом
    processed_by = Column(Integer, ForeignKey('users.id'), nullable=True)
    processed_at = Column(AwareDateTime(), nullable=True)
    admin_comment = Column(Text, nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', foreign_keys=[user_id], backref='withdrawal_requests')
    admin = relationship('User', foreign_keys=[processed_by])

    @property
    def amount_rubles(self) -> float:
        return self.amount_kopeks / 100


class PartnerApplication(Base):
    """Заявка на получение статуса партнёра."""

    __tablename__ = 'partner_applications'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)

    company_name = Column(String(255), nullable=True)
    website_url = Column(String(500), nullable=True)
    telegram_channel = Column(String(255), nullable=True)
    description = Column(Text, nullable=True)
    expected_monthly_referrals = Column(Integer, nullable=True)
    desired_commission_percent = Column(Integer, nullable=True)

    status = Column(String(20), default=PartnerStatus.PENDING.value, nullable=False)

    # Обработка админом
    admin_comment = Column(Text, nullable=True)
    approved_commission_percent = Column(Integer, nullable=True)
    processed_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    processed_at = Column(AwareDateTime(), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', foreign_keys=[user_id], backref='partner_applications')
    admin = relationship('User', foreign_keys=[processed_by])


class ReferralContest(Base):
    __tablename__ = 'referral_contests'

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(255), nullable=False)
    description = Column(Text, nullable=True)
    prize_text = Column(Text, nullable=True)
    contest_type = Column(String(50), nullable=False, default='referral_paid')
    start_at = Column(AwareDateTime(), nullable=False)
    end_at = Column(AwareDateTime(), nullable=False)
    daily_summary_time = Column(Time, nullable=False, default=time(hour=12, minute=0))
    daily_summary_times = Column(String(255), nullable=True)  # CSV HH:MM
    timezone = Column(String(64), nullable=False, default='UTC')
    is_active = Column(Boolean, nullable=False, default=True)
    last_daily_summary_date = Column(Date, nullable=True)
    last_daily_summary_at = Column(AwareDateTime(), nullable=True)
    final_summary_sent = Column(Boolean, nullable=False, default=False)
    created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    creator = relationship('User', backref='created_referral_contests')
    events = relationship(
        'ReferralContestEvent',
        back_populates='contest',
        cascade='all, delete-orphan',
    )

    def __repr__(self):
        return f"<ReferralContest id={self.id} title='{self.title}'>"


class ReferralContestEvent(Base):
    __tablename__ = 'referral_contest_events'
    __table_args__ = (
        UniqueConstraint(
            'contest_id',
            'referral_id',
            name='uq_referral_contest_referral',
        ),
        Index('idx_referral_contest_referrer', 'contest_id', 'referrer_id'),
    )

    id = Column(Integer, primary_key=True, index=True)
    contest_id = Column(Integer, ForeignKey('referral_contests.id', ondelete='CASCADE'), nullable=False)
    referrer_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    referral_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    event_type = Column(String(50), nullable=False)
    amount_kopeks = Column(Integer, nullable=False, default=0)
    occurred_at = Column(AwareDateTime(), nullable=False, default=func.now())

    contest = relationship('ReferralContest', back_populates='events')
    referrer = relationship('User', foreign_keys=[referrer_id])
    referral = relationship('User', foreign_keys=[referral_id])

    def __repr__(self):
        return (
            f'<ReferralContestEvent contest={self.contest_id} referrer={self.referrer_id} referral={self.referral_id}>'
        )


class ReferralContestVirtualParticipant(Base):
    __tablename__ = 'referral_contest_virtual_participants'

    id = Column(Integer, primary_key=True, index=True)
    contest_id = Column(Integer, ForeignKey('referral_contests.id', ondelete='CASCADE'), nullable=False)
    display_name = Column(String(255), nullable=False)
    referral_count = Column(Integer, nullable=False, default=0)
    total_amount_kopeks = Column(Integer, nullable=False, default=0)
    created_at = Column(AwareDateTime(), default=func.now())

    contest = relationship('ReferralContest')

    def __repr__(self):
        return (
            f"<ReferralContestVirtualParticipant id={self.id} name='{self.display_name}' count={self.referral_count}>"
        )


class ContestTemplate(Base):
    __tablename__ = 'contest_templates'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(100), nullable=False)
    slug = Column(String(50), nullable=False, unique=True, index=True)
    description = Column(Text, nullable=True)
    prize_type = Column(String(20), nullable=False, default='days')
    prize_value = Column(String(50), nullable=False, default='1')
    max_winners = Column(Integer, nullable=False, default=1)
    attempts_per_user = Column(Integer, nullable=False, default=1)
    times_per_day = Column(Integer, nullable=False, default=1)
    schedule_times = Column(String(255), nullable=True)  # CSV of HH:MM in local TZ
    cooldown_hours = Column(Integer, nullable=False, default=24)
    payload = Column(JSON, nullable=True)
    is_enabled = Column(Boolean, nullable=False, default=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    rounds = relationship('ContestRound', back_populates='template')


class ContestRound(Base):
    __tablename__ = 'contest_rounds'
    __table_args__ = (
        Index('idx_contest_round_status', 'status'),
        Index('idx_contest_round_template', 'template_id'),
    )

    id = Column(Integer, primary_key=True, index=True)
    template_id = Column(Integer, ForeignKey('contest_templates.id', ondelete='CASCADE'), nullable=False)
    starts_at = Column(AwareDateTime(), nullable=False)
    ends_at = Column(AwareDateTime(), nullable=False)
    status = Column(String(20), nullable=False, default='active')  # active, finished
    payload = Column(JSON, nullable=True)
    winners_count = Column(Integer, nullable=False, default=0)
    max_winners = Column(Integer, nullable=False, default=1)
    attempts_per_user = Column(Integer, nullable=False, default=1)
    message_id = Column(BigInteger, nullable=True)
    chat_id = Column(BigInteger, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    template = relationship('ContestTemplate', back_populates='rounds')
    attempts = relationship('ContestAttempt', back_populates='round', cascade='all, delete-orphan')


class ContestAttempt(Base):
    __tablename__ = 'contest_attempts'
    __table_args__ = (
        UniqueConstraint('round_id', 'user_id', name='uq_round_user_attempt'),
        Index('idx_contest_attempt_round', 'round_id'),
    )

    id = Column(Integer, primary_key=True, index=True)
    round_id = Column(Integer, ForeignKey('contest_rounds.id', ondelete='CASCADE'), nullable=False)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    answer = Column(Text, nullable=True)
    is_winner = Column(Boolean, nullable=False, default=False)
    created_at = Column(AwareDateTime(), default=func.now())

    round = relationship('ContestRound', back_populates='attempts')
    user = relationship('User')


class Squad(Base):
    __tablename__ = 'squads'

    id = Column(Integer, primary_key=True, index=True)

    uuid = Column(String(255), unique=True, nullable=False)
    name = Column(String(255), nullable=False)
    country_code = Column(String(5), nullable=True)

    is_available = Column(Boolean, default=True)
    price_kopeks = Column(Integer, default=0)

    description = Column(Text, nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    @property
    def price_rubles(self) -> float:
        return self.price_kopeks / 100


class ServiceRule(Base):
    __tablename__ = 'service_rules'

    id = Column(Integer, primary_key=True, index=True)

    order = Column(Integer, default=0)
    title = Column(String(255), nullable=False)

    content = Column(Text, nullable=False)

    is_active = Column(Boolean, default=True)

    language = Column(String(5), default='ru')

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())


class PrivacyPolicy(Base):
    __tablename__ = 'privacy_policies'

    id = Column(Integer, primary_key=True, index=True)
    language = Column(String(10), nullable=False, unique=True)
    content = Column(Text, nullable=False)
    is_enabled = Column(Boolean, default=True, nullable=False)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())


class PublicOffer(Base):
    __tablename__ = 'public_offers'

    id = Column(Integer, primary_key=True, index=True)
    language = Column(String(10), nullable=False, unique=True)
    content = Column(Text, nullable=False)
    is_enabled = Column(Boolean, default=True, nullable=False)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())


class FaqSetting(Base):
    __tablename__ = 'faq_settings'

    id = Column(Integer, primary_key=True, index=True)
    language = Column(String(10), nullable=False, unique=True)
    is_enabled = Column(Boolean, default=True, nullable=False)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())


class FaqPage(Base):
    __tablename__ = 'faq_pages'

    id = Column(Integer, primary_key=True, index=True)
    language = Column(String(10), nullable=False, index=True)
    title = Column(String(255), nullable=False)
    content = Column(Text, nullable=False)
    display_order = Column(Integer, default=0, nullable=False)
    is_active = Column(Boolean, default=True, nullable=False)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())


class SystemSetting(Base):
    __tablename__ = 'system_settings'

    id = Column(Integer, primary_key=True, index=True)
    key = Column(String(255), unique=True, nullable=False)
    value = Column(Text, nullable=True)
    description = Column(Text, nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())


class MonitoringLog(Base):
    __tablename__ = 'monitoring_logs'

    id = Column(Integer, primary_key=True, index=True)

    event_type = Column(String(100), nullable=False)

    message = Column(Text, nullable=False)
    data = Column(JSON, nullable=True)

    is_success = Column(Boolean, default=True)

    created_at = Column(AwareDateTime(), default=func.now())


class SentNotification(Base):
    __tablename__ = 'sent_notifications'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='CASCADE'), nullable=False)
    notification_type = Column(String(50), nullable=False)
    days_before = Column(Integer, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())

    user = relationship('User', backref='sent_notifications')
    subscription = relationship('Subscription', backref=backref('sent_notifications', passive_deletes=True))


class SubscriptionEvent(Base):
    __tablename__ = 'subscription_events'

    id = Column(Integer, primary_key=True, index=True)
    event_type = Column(String(50), nullable=False)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='SET NULL'), nullable=True)
    transaction_id = Column(Integer, ForeignKey('transactions.id', ondelete='SET NULL'), nullable=True)
    amount_kopeks = Column(Integer, nullable=True)
    currency = Column(String(16), nullable=True)
    message = Column(Text, nullable=True)
    occurred_at = Column(AwareDateTime(), nullable=False, default=func.now())
    extra = Column(JSON, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())

    user = relationship('User', backref='subscription_events')
    subscription = relationship('Subscription', backref='subscription_events')
    transaction = relationship('Transaction', backref='subscription_events')


class DiscountOffer(Base):
    __tablename__ = 'discount_offers'
    __table_args__ = (Index('ix_discount_offers_user_type', 'user_id', 'notification_type'),)

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='SET NULL'), nullable=True)
    notification_type = Column(String(50), nullable=False)
    discount_percent = Column(Integer, nullable=False, default=0)
    bonus_amount_kopeks = Column(Integer, nullable=False, default=0)
    expires_at = Column(AwareDateTime(), nullable=False)
    claimed_at = Column(AwareDateTime(), nullable=True)
    is_active = Column(Boolean, default=True, nullable=False)
    effect_type = Column(String(50), nullable=False, default='percent_discount')
    extra_data = Column(JSON, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    user = relationship('User', back_populates='discount_offers')
    subscription = relationship('Subscription', back_populates='discount_offers')
    logs = relationship('PromoOfferLog', back_populates='offer')


class PromoOfferTemplate(Base):
    __tablename__ = 'promo_offer_templates'
    __table_args__ = (Index('ix_promo_offer_templates_type', 'offer_type'),)

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(255), nullable=False)
    offer_type = Column(String(50), nullable=False)
    message_text = Column(Text, nullable=False)
    button_text = Column(String(255), nullable=False)
    valid_hours = Column(Integer, nullable=False, default=24)
    discount_percent = Column(Integer, nullable=False, default=0)
    bonus_amount_kopeks = Column(Integer, nullable=False, default=0)
    active_discount_hours = Column(Integer, nullable=True)
    test_duration_hours = Column(Integer, nullable=True)
    test_squad_uuids = Column(JSON, default=list)
    is_active = Column(Boolean, default=True, nullable=False)
    created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    creator = relationship('User')


class SubscriptionTemporaryAccess(Base):
    __tablename__ = 'subscription_temporary_access'

    id = Column(Integer, primary_key=True, index=True)
    subscription_id = Column(Integer, ForeignKey('subscriptions.id', ondelete='CASCADE'), nullable=False)
    offer_id = Column(Integer, ForeignKey('discount_offers.id', ondelete='CASCADE'), nullable=False)
    squad_uuid = Column(String(255), nullable=False)
    expires_at = Column(AwareDateTime(), nullable=False)
    created_at = Column(AwareDateTime(), default=func.now())
    deactivated_at = Column(AwareDateTime(), nullable=True)
    is_active = Column(Boolean, default=True, nullable=False)
    was_already_connected = Column(Boolean, default=False, nullable=False)

    subscription = relationship('Subscription', back_populates='temporary_accesses')
    offer = relationship('DiscountOffer')


class PromoOfferLog(Base):
    __tablename__ = 'promo_offer_logs'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True)
    offer_id = Column(Integer, ForeignKey('discount_offers.id', ondelete='SET NULL'), nullable=True, index=True)
    action = Column(String(50), nullable=False)
    source = Column(String(100), nullable=True)
    percent = Column(Integer, nullable=True)
    effect_type = Column(String(50), nullable=True)
    details = Column(JSON, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())

    user = relationship('User', back_populates='promo_offer_logs')
    offer = relationship('DiscountOffer', back_populates='logs')


class BroadcastHistory(Base):
    __tablename__ = 'broadcast_history'

    id = Column(Integer, primary_key=True, index=True)
    target_type = Column(String(100), nullable=False)
    message_text = Column(Text, nullable=True)  # Nullable for email-only broadcasts
    has_media = Column(Boolean, default=False)
    media_type = Column(String(20), nullable=True)
    media_file_id = Column(String(255), nullable=True)
    media_caption = Column(Text, nullable=True)
    total_count = Column(Integer, default=0)
    sent_count = Column(Integer, default=0)
    failed_count = Column(Integer, default=0)
    blocked_count = Column(Integer, default=0)
    status = Column(String(50), default='in_progress')
    admin_id = Column(Integer, ForeignKey('users.id'))
    admin_name = Column(String(255))
    created_at = Column(AwareDateTime(), server_default=func.now())
    completed_at = Column(AwareDateTime(), nullable=True)

    # Email broadcast fields
    channel = Column(String(20), default='telegram', nullable=False)  # telegram|email|both
    email_subject = Column(String(255), nullable=True)
    email_html_content = Column(Text, nullable=True)

    admin = relationship('User', back_populates='broadcasts')


class Poll(Base):
    __tablename__ = 'polls'

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(255), nullable=False)
    description = Column(Text, nullable=True)
    reward_enabled = Column(Boolean, nullable=False, default=False)
    reward_amount_kopeks = Column(Integer, nullable=False, default=0)
    created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now(), nullable=False)
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now(), nullable=False)

    creator = relationship('User', backref='created_polls', foreign_keys=[created_by])
    questions = relationship(
        'PollQuestion',
        back_populates='poll',
        cascade='all, delete-orphan',
        order_by='PollQuestion.order',
    )
    responses = relationship(
        'PollResponse',
        back_populates='poll',
        cascade='all, delete-orphan',
    )


class PollQuestion(Base):
    __tablename__ = 'poll_questions'

    id = Column(Integer, primary_key=True, index=True)
    poll_id = Column(Integer, ForeignKey('polls.id', ondelete='CASCADE'), nullable=False, index=True)
    text = Column(Text, nullable=False)
    order = Column(Integer, nullable=False, default=0)

    poll = relationship('Poll', back_populates='questions')
    options = relationship(
        'PollOption',
        back_populates='question',
        cascade='all, delete-orphan',
        order_by='PollOption.order',
    )
    answers = relationship('PollAnswer', back_populates='question')


class PollOption(Base):
    __tablename__ = 'poll_options'

    id = Column(Integer, primary_key=True, index=True)
    question_id = Column(Integer, ForeignKey('poll_questions.id', ondelete='CASCADE'), nullable=False, index=True)
    text = Column(Text, nullable=False)
    order = Column(Integer, nullable=False, default=0)

    question = relationship('PollQuestion', back_populates='options')
    answers = relationship('PollAnswer', back_populates='option')


class PollResponse(Base):
    __tablename__ = 'poll_responses'

    id = Column(Integer, primary_key=True, index=True)
    poll_id = Column(Integer, ForeignKey('polls.id', ondelete='CASCADE'), nullable=False, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True)
    sent_at = Column(AwareDateTime(), default=func.now(), nullable=False)
    started_at = Column(AwareDateTime(), nullable=True)
    completed_at = Column(AwareDateTime(), nullable=True)
    reward_given = Column(Boolean, nullable=False, default=False)
    reward_amount_kopeks = Column(Integer, nullable=False, default=0)

    poll = relationship('Poll', back_populates='responses')
    user = relationship('User', back_populates='poll_responses')
    answers = relationship(
        'PollAnswer',
        back_populates='response',
        cascade='all, delete-orphan',
    )

    __table_args__ = (UniqueConstraint('poll_id', 'user_id', name='uq_poll_user'),)


class PollAnswer(Base):
    __tablename__ = 'poll_answers'

    id = Column(Integer, primary_key=True, index=True)
    response_id = Column(Integer, ForeignKey('poll_responses.id', ondelete='CASCADE'), nullable=False, index=True)
    question_id = Column(Integer, ForeignKey('poll_questions.id', ondelete='CASCADE'), nullable=False, index=True)
    option_id = Column(Integer, ForeignKey('poll_options.id', ondelete='CASCADE'), nullable=False, index=True)
    created_at = Column(AwareDateTime(), default=func.now(), nullable=False)

    response = relationship('PollResponse', back_populates='answers')
    question = relationship('PollQuestion', back_populates='answers')
    option = relationship('PollOption', back_populates='answers')

    __table_args__ = (UniqueConstraint('response_id', 'question_id', name='uq_poll_answer_unique'),)


class ServerSquad(Base):
    __tablename__ = 'server_squads'

    id = Column(Integer, primary_key=True, index=True)

    squad_uuid = Column(String(255), unique=True, nullable=False, index=True)

    display_name = Column(String(255), nullable=False)

    original_name = Column(String(255), nullable=True)

    country_code = Column(String(5), nullable=True)

    is_available = Column(Boolean, default=True)
    is_trial_eligible = Column(Boolean, default=False, nullable=False)

    price_kopeks = Column(Integer, default=0)

    description = Column(Text, nullable=True)

    sort_order = Column(Integer, default=0)

    max_users = Column(Integer, nullable=True)
    current_users = Column(Integer, default=0)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    allowed_promo_groups = relationship(
        'PromoGroup',
        secondary=server_squad_promo_groups,
        back_populates='server_squads',
        lazy='selectin',
    )

    @property
    def price_rubles(self) -> float:
        return self.price_kopeks / 100

    @property
    def is_full(self) -> bool:
        if self.max_users is None:
            return False
        return self.current_users >= self.max_users

    @property
    def availability_status(self) -> str:
        if not self.is_available:
            return 'Недоступен'
        if self.is_full:
            return 'Переполнен'
        return 'Доступен'


class SubscriptionServer(Base):
    __tablename__ = 'subscription_servers'

    id = Column(Integer, primary_key=True, index=True)
    subscription_id = Column(Integer, ForeignKey('subscriptions.id'), nullable=False)
    server_squad_id = Column(Integer, ForeignKey('server_squads.id'), nullable=False)

    connected_at = Column(AwareDateTime(), default=func.now())

    paid_price_kopeks = Column(Integer, default=0)

    subscription = relationship('Subscription', backref=backref('subscription_servers', passive_deletes=True))
    server_squad = relationship('ServerSquad', backref='subscription_servers')


class SupportAuditLog(Base):
    __tablename__ = 'support_audit_logs'

    id = Column(Integer, primary_key=True, index=True)
    actor_user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    actor_telegram_id = Column(BigInteger, nullable=True)  # Can be None for email-only users
    is_moderator = Column(Boolean, default=False)
    action = Column(String(50), nullable=False)  # close_ticket, block_user_timed, block_user_perm, unblock_user
    ticket_id = Column(Integer, ForeignKey('tickets.id', ondelete='SET NULL'), nullable=True)
    target_user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    details = Column(JSON, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())

    actor = relationship('User', foreign_keys=[actor_user_id])
    ticket = relationship('Ticket', foreign_keys=[ticket_id])


class UserMessage(Base):
    __tablename__ = 'user_messages'
    id = Column(Integer, primary_key=True, index=True)
    message_text = Column(Text, nullable=False)
    is_active = Column(Boolean, default=True)
    sort_order = Column(Integer, default=0)
    created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())
    creator = relationship('User', backref='created_messages')

    def __repr__(self):
        return f"<UserMessage(id={self.id}, active={self.is_active}, text='{self.message_text[:50]}...')>"


class WelcomeText(Base):
    __tablename__ = 'welcome_texts'

    id = Column(Integer, primary_key=True, index=True)
    text_content = Column(Text, nullable=False)
    is_active = Column(Boolean, default=True)
    is_enabled = Column(Boolean, default=True)
    created_by = Column(Integer, ForeignKey('users.id'), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    creator = relationship('User', backref='created_welcome_texts')


class PinnedMessage(Base):
    __tablename__ = 'pinned_messages'

    id = Column(Integer, primary_key=True, index=True)
    content = Column(Text, nullable=False, default='')
    media_type = Column(String(32), nullable=True)
    media_file_id = Column(String(255), nullable=True)
    send_before_menu = Column(Boolean, nullable=False, server_default='1', default=True)
    send_on_every_start = Column(Boolean, nullable=False, server_default='1', default=True)
    is_active = Column(Boolean, default=True)
    created_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    creator = relationship('User', backref='pinned_messages')


class AdvertisingCampaign(Base):
    __tablename__ = 'advertising_campaigns'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(255), nullable=False)
    start_parameter = Column(String(64), nullable=False, unique=True, index=True)
    bonus_type = Column(String(20), nullable=False)

    balance_bonus_kopeks = Column(Integer, default=0)

    subscription_duration_days = Column(Integer, nullable=True)
    subscription_traffic_gb = Column(Integer, nullable=True)
    subscription_device_limit = Column(Integer, nullable=True)
    subscription_squads = Column(JSON, default=list)

    # Поля для типа "tariff" - выдача тарифа
    tariff_id = Column(Integer, ForeignKey('tariffs.id', ondelete='SET NULL'), nullable=True)
    tariff_duration_days = Column(Integer, nullable=True)

    is_active = Column(Boolean, default=True)

    # Привязка к партнёру
    partner_user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True)

    created_by = Column(Integer, ForeignKey('users.id'), nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    registrations = relationship('AdvertisingCampaignRegistration', back_populates='campaign')
    tariff = relationship('Tariff', foreign_keys=[tariff_id])
    partner = relationship('User', foreign_keys=[partner_user_id])

    @property
    def is_balance_bonus(self) -> bool:
        return self.bonus_type == 'balance'

    @property
    def is_subscription_bonus(self) -> bool:
        return self.bonus_type == 'subscription'

    @property
    def is_none_bonus(self) -> bool:
        """Ссылка без награды - только для отслеживания."""
        return self.bonus_type == 'none'

    @property
    def is_tariff_bonus(self) -> bool:
        """Выдача тарифа на определённое время."""
        return self.bonus_type == 'tariff'


class AdvertisingCampaignRegistration(Base):
    __tablename__ = 'advertising_campaign_registrations'
    __table_args__ = (UniqueConstraint('campaign_id', 'user_id', name='uq_campaign_user'),)

    id = Column(Integer, primary_key=True, index=True)
    campaign_id = Column(Integer, ForeignKey('advertising_campaigns.id', ondelete='CASCADE'), nullable=False)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)

    bonus_type = Column(String(20), nullable=False)
    balance_bonus_kopeks = Column(Integer, default=0)
    subscription_duration_days = Column(Integer, nullable=True)

    # Поля для типа "tariff"
    tariff_id = Column(Integer, ForeignKey('tariffs.id', ondelete='SET NULL'), nullable=True)
    tariff_duration_days = Column(Integer, nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())

    campaign = relationship('AdvertisingCampaign', back_populates='registrations')
    user = relationship('User')
    tariff = relationship('Tariff')

    @property
    def balance_bonus_rubles(self) -> float:
        return (self.balance_bonus_kopeks or 0) / 100


class TicketStatus(Enum):
    OPEN = 'open'
    ANSWERED = 'answered'
    CLOSED = 'closed'
    PENDING = 'pending'


class Ticket(Base):
    __tablename__ = 'tickets'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)

    title = Column(String(255), nullable=False)
    status = Column(String(20), default=TicketStatus.OPEN.value, nullable=False)
    priority = Column(String(20), default='normal', nullable=False)  # low, normal, high, urgent
    # Блокировка ответов пользователя в этом тикете
    user_reply_block_permanent = Column(Boolean, default=False, nullable=False)
    user_reply_block_until = Column(AwareDateTime(), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())
    closed_at = Column(AwareDateTime(), nullable=True)
    # SLA reminders
    last_sla_reminder_at = Column(AwareDateTime(), nullable=True)

    # Связи
    user = relationship('User', backref='tickets')
    messages = relationship('TicketMessage', back_populates='ticket', cascade='all, delete-orphan')

    @property
    def is_open(self) -> bool:
        return self.status == TicketStatus.OPEN.value

    @property
    def is_answered(self) -> bool:
        return self.status == TicketStatus.ANSWERED.value

    @property
    def is_closed(self) -> bool:
        return self.status == TicketStatus.CLOSED.value

    @property
    def is_pending(self) -> bool:
        return self.status == TicketStatus.PENDING.value

    @property
    def is_user_reply_blocked(self) -> bool:
        if self.user_reply_block_permanent:
            return True
        if self.user_reply_block_until:
            return _aware(self.user_reply_block_until) > datetime.now(UTC)
        return False

    @property
    def status_emoji(self) -> str:
        status_emojis = {
            TicketStatus.OPEN.value: '🔴',
            TicketStatus.ANSWERED.value: '🟡',
            TicketStatus.CLOSED.value: '🟢',
            TicketStatus.PENDING.value: '⏳',
        }
        return status_emojis.get(self.status, '❓')

    @property
    def priority_emoji(self) -> str:
        priority_emojis = {'low': '🟢', 'normal': '🟡', 'high': '🟠', 'urgent': '🔴'}
        return priority_emojis.get(self.priority, '🟡')

    def __repr__(self):
        return f"<Ticket(id={self.id}, user_id={self.user_id}, status={self.status}, title='{self.title[:30]}...')>"


class TicketMessage(Base):
    __tablename__ = 'ticket_messages'

    id = Column(Integer, primary_key=True, index=True)
    ticket_id = Column(Integer, ForeignKey('tickets.id', ondelete='CASCADE'), nullable=False)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)

    message_text = Column(Text, nullable=False)
    is_from_admin = Column(Boolean, default=False, nullable=False)

    # Для медиа файлов
    has_media = Column(Boolean, default=False)
    media_type = Column(String(20), nullable=True)  # photo, video, document, voice, etc.
    media_file_id = Column(String(255), nullable=True)
    media_caption = Column(Text, nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())

    # Связи
    ticket = relationship('Ticket', back_populates='messages')
    user = relationship('User')

    @property
    def is_user_message(self) -> bool:
        return not self.is_from_admin

    @property
    def is_admin_message(self) -> bool:
        return self.is_from_admin

    def __repr__(self):
        return f"<TicketMessage(id={self.id}, ticket_id={self.ticket_id}, is_admin={self.is_from_admin}, text='{self.message_text[:30]}...')>"


class WebApiToken(Base):
    __tablename__ = 'web_api_tokens'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(255), nullable=False)
    token_hash = Column(String(128), nullable=False, unique=True, index=True)
    token_prefix = Column(String(32), nullable=False, index=True)
    description = Column(Text, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())
    expires_at = Column(AwareDateTime(), nullable=True)
    last_used_at = Column(AwareDateTime(), nullable=True)
    last_used_ip = Column(String(64), nullable=True)
    is_active = Column(Boolean, default=True, nullable=False)
    created_by = Column(String(255), nullable=True)

    def __repr__(self) -> str:
        status = 'active' if self.is_active else 'revoked'
        return f"<WebApiToken id={self.id} name='{self.name}' status={status}>"


class MainMenuButton(Base):
    __tablename__ = 'main_menu_buttons'

    id = Column(Integer, primary_key=True, index=True)
    text = Column(String(64), nullable=False)
    action_type = Column(String(20), nullable=False)
    action_value = Column(Text, nullable=False)
    visibility = Column(String(20), nullable=False, default=MainMenuButtonVisibility.ALL.value)
    is_active = Column(Boolean, nullable=False, default=True)
    display_order = Column(Integer, nullable=False, default=0)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    __table_args__ = (Index('ix_main_menu_buttons_order', 'display_order', 'id'),)

    @property
    def action_type_enum(self) -> MainMenuButtonActionType:
        try:
            return MainMenuButtonActionType(self.action_type)
        except ValueError:
            return MainMenuButtonActionType.URL

    @property
    def visibility_enum(self) -> MainMenuButtonVisibility:
        try:
            return MainMenuButtonVisibility(self.visibility)
        except ValueError:
            return MainMenuButtonVisibility.ALL

    def __repr__(self) -> str:
        return (
            f"<MainMenuButton id={self.id} text='{self.text}' "
            f'action={self.action_type} visibility={self.visibility} active={self.is_active}>'
        )


class MenuLayoutHistory(Base):
    """История изменений конфигурации меню."""

    __tablename__ = 'menu_layout_history'

    id = Column(Integer, primary_key=True, index=True)
    config_json = Column(Text, nullable=False)  # Полная конфигурация в JSON
    action = Column(String(50), nullable=False)  # update, reset, import
    changes_summary = Column(Text, nullable=True)  # Краткое описание изменений
    user_info = Column(String(255), nullable=True)  # Информация о пользователе/токене
    created_at = Column(AwareDateTime(), default=func.now(), index=True)

    __table_args__ = (Index('ix_menu_layout_history_created', 'created_at'),)

    def __repr__(self) -> str:
        return f"<MenuLayoutHistory id={self.id} action='{self.action}' created_at={self.created_at}>"


class ButtonClickLog(Base):
    """Логи кликов по кнопкам меню."""

    __tablename__ = 'button_click_logs'

    id = Column(Integer, primary_key=True, index=True)
    button_id = Column(String(100), nullable=False, index=True)  # ID кнопки
    user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True)
    callback_data = Column(String(255), nullable=True)  # callback_data кнопки
    clicked_at = Column(AwareDateTime(), default=func.now(), index=True)

    # Дополнительная информация
    button_type = Column(String(20), nullable=True, index=True)  # builtin, callback, url, mini_app
    button_text = Column(String(255), nullable=True)  # Текст кнопки на момент клика

    __table_args__ = (
        Index('ix_button_click_logs_button_date', 'button_id', 'clicked_at'),
        Index('ix_button_click_logs_user_date', 'user_id', 'clicked_at'),
    )

    # Связи
    user = relationship('User', foreign_keys=[user_id])

    def __repr__(self) -> str:
        return f"<ButtonClickLog id={self.id} button='{self.button_id}' user={self.user_id} at={self.clicked_at}>"


class Webhook(Base):
    """Webhook конфигурация для подписки на события."""

    __tablename__ = 'webhooks'
    __table_args__ = (
        Index('ix_webhooks_event_type', 'event_type'),
        Index('ix_webhooks_is_active', 'is_active'),
    )

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(255), nullable=False)
    url = Column(Text, nullable=False)
    secret = Column(String(128), nullable=True)  # Секрет для подписи payload
    event_type = Column(String(50), nullable=False)  # user.created, payment.completed, ticket.created, etc.
    is_active = Column(Boolean, default=True, nullable=False)
    description = Column(Text, nullable=True)
    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())
    last_triggered_at = Column(AwareDateTime(), nullable=True)
    failure_count = Column(Integer, default=0, nullable=False)
    success_count = Column(Integer, default=0, nullable=False)

    deliveries = relationship('WebhookDelivery', back_populates='webhook', cascade='all, delete-orphan')

    def __repr__(self) -> str:
        status = 'active' if self.is_active else 'inactive'
        return f"<Webhook id={self.id} name='{self.name}' event='{self.event_type}' status={status}>"


class WebhookDelivery(Base):
    """История доставки webhooks."""

    __tablename__ = 'webhook_deliveries'
    __table_args__ = (
        Index('ix_webhook_deliveries_webhook_created', 'webhook_id', 'created_at'),
        Index('ix_webhook_deliveries_status', 'status'),
    )

    id = Column(Integer, primary_key=True, index=True)
    webhook_id = Column(Integer, ForeignKey('webhooks.id', ondelete='CASCADE'), nullable=False)
    event_type = Column(String(50), nullable=False)
    payload = Column(JSON, nullable=False)  # Отправленный payload
    response_status = Column(Integer, nullable=True)  # HTTP статус ответа
    response_body = Column(Text, nullable=True)  # Тело ответа (может быть обрезано)
    status = Column(String(20), nullable=False)  # pending, success, failed
    error_message = Column(Text, nullable=True)
    attempt_number = Column(Integer, default=1, nullable=False)
    created_at = Column(AwareDateTime(), default=func.now())
    delivered_at = Column(AwareDateTime(), nullable=True)
    next_retry_at = Column(AwareDateTime(), nullable=True)

    webhook = relationship('Webhook', back_populates='deliveries')

    def __repr__(self) -> str:
        return f"<WebhookDelivery id={self.id} webhook_id={self.webhook_id} status='{self.status}' event='{self.event_type}'>"


class CabinetRefreshToken(Base):
    """Refresh tokens for cabinet JWT authentication."""

    __tablename__ = 'cabinet_refresh_tokens'
    __table_args__ = (Index('ix_cabinet_refresh_tokens_user', 'user_id'),)

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    token_hash = Column(String(255), unique=True, nullable=False, index=True)
    device_info = Column(String(500), nullable=True)
    expires_at = Column(AwareDateTime(), nullable=False)
    created_at = Column(AwareDateTime(), default=func.now())
    revoked_at = Column(AwareDateTime(), nullable=True)

    user = relationship('User', backref='cabinet_tokens')

    @property
    def is_expired(self) -> bool:
        return datetime.now(UTC) > _aware(self.expires_at)

    @property
    def is_revoked(self) -> bool:
        return self.revoked_at is not None

    @property
    def is_valid(self) -> bool:
        return not self.is_expired and not self.is_revoked

    def __repr__(self) -> str:
        status = 'valid' if self.is_valid else ('revoked' if self.is_revoked else 'expired')
        return f'<CabinetRefreshToken id={self.id} user_id={self.user_id} status={status}>'


# ==================== FORTUNE WHEEL ====================


class WheelConfig(Base):
    """Глобальная конфигурация колеса удачи."""

    __tablename__ = 'wheel_configs'

    id = Column(Integer, primary_key=True, index=True)

    # Основные настройки
    is_enabled = Column(Boolean, default=False, nullable=False)
    name = Column(String(255), default='Колесо удачи', nullable=False)

    # Стоимость спина
    spin_cost_stars = Column(Integer, default=10, nullable=False)  # Стоимость в Stars
    spin_cost_days = Column(Integer, default=1, nullable=False)  # Стоимость в днях подписки
    spin_cost_stars_enabled = Column(Boolean, default=True, nullable=False)
    spin_cost_days_enabled = Column(Boolean, default=True, nullable=False)

    # RTP настройки (Return to Player) - процент возврата 0-100
    rtp_percent = Column(Integer, default=80, nullable=False)

    # Лимиты
    daily_spin_limit = Column(Integer, default=5, nullable=False)  # 0 = без лимита
    min_subscription_days_for_day_payment = Column(Integer, default=3, nullable=False)

    # Генерация промокодов
    promo_prefix = Column(String(20), default='WHEEL', nullable=False)
    promo_validity_days = Column(Integer, default=7, nullable=False)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    prizes = relationship('WheelPrize', back_populates='config', cascade='all, delete-orphan')

    def __repr__(self) -> str:
        return f'<WheelConfig id={self.id} enabled={self.is_enabled} rtp={self.rtp_percent}%>'


class WheelPrize(Base):
    """Приз на колесе удачи."""

    __tablename__ = 'wheel_prizes'

    id = Column(Integer, primary_key=True, index=True)
    config_id = Column(Integer, ForeignKey('wheel_configs.id', ondelete='CASCADE'), nullable=False)

    # Тип и значение приза
    prize_type = Column(String(50), nullable=False)  # WheelPrizeType
    prize_value = Column(Integer, default=0, nullable=False)  # Дни/копейки/GB в зависимости от типа

    # Отображение
    display_name = Column(String(100), nullable=False)
    emoji = Column(String(10), default='🎁', nullable=False)
    color = Column(String(20), default='#3B82F6', nullable=False)  # HEX цвет сектора

    # Стоимость приза для расчета RTP (в копейках)
    prize_value_kopeks = Column(Integer, default=0, nullable=False)

    # Порядок и вероятность
    sort_order = Column(Integer, default=0, nullable=False)
    manual_probability = Column(Float, nullable=True)  # Если задано - игнорирует RTP расчет (0.0-1.0)
    is_active = Column(Boolean, default=True, nullable=False)

    # Настройки генерируемого промокода (только для prize_type=promocode)
    promo_balance_bonus_kopeks = Column(Integer, default=0)
    promo_subscription_days = Column(Integer, default=0)
    promo_traffic_gb = Column(Integer, default=0)

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    config = relationship('WheelConfig', back_populates='prizes')
    spins = relationship('WheelSpin', back_populates='prize')

    def __repr__(self) -> str:
        return f"<WheelPrize id={self.id} type={self.prize_type} name='{self.display_name}'>"


class WheelSpin(Base):
    """История спинов колеса удачи."""

    __tablename__ = 'wheel_spins'
    __table_args__ = (Index('ix_wheel_spins_user_created', 'user_id', 'created_at'),)

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    prize_id = Column(Integer, ForeignKey('wheel_prizes.id', ondelete='SET NULL'), nullable=True)

    # Способ оплаты
    payment_type = Column(String(50), nullable=False)  # WheelSpinPaymentType
    payment_amount = Column(Integer, nullable=False)  # Stars или дни
    payment_value_kopeks = Column(Integer, nullable=False)  # Эквивалент в копейках для статистики

    # Результат
    prize_type = Column(String(50), nullable=False)  # Копируем из WheelPrize на момент спина
    prize_value = Column(Integer, nullable=False)
    prize_display_name = Column(String(100), nullable=False)
    prize_value_kopeks = Column(Integer, nullable=False)  # Стоимость приза в копейках

    # Сгенерированный промокод (если приз - промокод)
    generated_promocode_id = Column(Integer, ForeignKey('promocodes.id'), nullable=True)

    # Флаг успешного начисления
    is_applied = Column(Boolean, default=False, nullable=False)
    applied_at = Column(AwareDateTime(), nullable=True)

    created_at = Column(AwareDateTime(), default=func.now())

    user = relationship('User', backref='wheel_spins')
    prize = relationship('WheelPrize', back_populates='spins')
    generated_promocode = relationship('PromoCode')

    @property
    def prize_value_rubles(self) -> float:
        """Стоимость приза в рублях."""
        return self.prize_value_kopeks / 100

    @property
    def payment_value_rubles(self) -> float:
        """Стоимость оплаты в рублях."""
        return self.payment_value_kopeks / 100

    def __repr__(self) -> str:
        return f"<WheelSpin id={self.id} user_id={self.user_id} prize='{self.prize_display_name}'>"


class TicketNotification(Base):
    """Уведомления о тикетах для кабинета (веб-интерфейс)."""

    __tablename__ = 'ticket_notifications'
    __table_args__ = (
        Index('ix_ticket_notifications_user_read', 'user_id', 'is_read'),
        Index('ix_ticket_notifications_admin_read', 'is_for_admin', 'is_read'),
    )

    id = Column(Integer, primary_key=True, index=True)
    ticket_id = Column(Integer, ForeignKey('tickets.id', ondelete='CASCADE'), nullable=False, index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True)

    # Тип уведомления: new_ticket, admin_reply, user_reply
    notification_type = Column(String(50), nullable=False)

    # Текст уведомления
    message = Column(Text, nullable=True)

    # Для админа или для пользователя
    is_for_admin = Column(Boolean, default=False, nullable=False)

    # Прочитано ли уведомление
    is_read = Column(Boolean, default=False, nullable=False)

    created_at = Column(AwareDateTime(), default=func.now())
    read_at = Column(AwareDateTime(), nullable=True)

    ticket = relationship('Ticket', backref='notifications')
    user = relationship('User', backref='ticket_notifications')

    def __repr__(self) -> str:
        return f'<TicketNotification id={self.id} type={self.notification_type} for_admin={self.is_for_admin}>'


# ==================== PAYMENT METHOD CONFIG ====================


class PaymentMethodConfig(Base):
    """Конфигурация отображения платёжных методов в кабинете."""

    __tablename__ = 'payment_method_configs'

    id = Column(Integer, primary_key=True, index=True)

    # Уникальный идентификатор метода (совпадает с PaymentMethod enum: 'yookassa', 'cryptobot' и т.д.)
    method_id = Column(String(50), unique=True, nullable=False, index=True)

    # Порядок отображения (меньше = выше)
    sort_order = Column(Integer, nullable=False, default=0, index=True)

    # Включён/выключен (дополнительно к env-переменным)
    is_enabled = Column(Boolean, nullable=False, default=True)

    # Переопределение отображаемого имени (null = использовать из env)
    display_name = Column(String(255), nullable=True)

    # Под-опции включения/выключения (JSON): {"card": true, "sbp": false}
    # Для методов с вариантами: yookassa, pal24, platega
    sub_options = Column(JSON, nullable=True, default=None)

    # Переопределение мин/макс сумм (null = из env)
    min_amount_kopeks = Column(Integer, nullable=True)
    max_amount_kopeks = Column(Integer, nullable=True)

    # --- Условия отображения ---

    # Фильтр по типу пользователя: 'all', 'telegram', 'email'
    user_type_filter = Column(String(20), nullable=False, default='all')

    # Фильтр по первому пополнению: 'any', 'yes' (делал), 'no' (не делал)
    first_topup_filter = Column(String(10), nullable=False, default='any')

    # Режим фильтра промо-групп: 'all' (все видят), 'selected' (только выбранные)
    promo_group_filter_mode = Column(String(20), nullable=False, default='all')

    # M2M связь с промогруппами
    allowed_promo_groups = relationship(
        'PromoGroup',
        secondary=payment_method_promo_groups,
        lazy='selectin',
    )

    created_at = Column(AwareDateTime(), default=func.now())
    updated_at = Column(AwareDateTime(), default=func.now(), onupdate=func.now())

    def __repr__(self) -> str:
        return f"<PaymentMethodConfig method_id='{self.method_id}' order={self.sort_order} enabled={self.is_enabled}>"


class RequiredChannel(Base):
    """Channels that users must subscribe to in order to use the bot."""

    __tablename__ = 'required_channels'

    id = Column(Integer, primary_key=True, autoincrement=True)
    channel_id = Column(String(100), unique=True, nullable=False)  # -100xxx numeric format (always string)
    channel_link = Column(String(500), nullable=True)  # https://t.me/xxx
    title = Column(String(255), nullable=True)  # Display name
    is_active = Column(Boolean, nullable=False, server_default='true')
    sort_order = Column(Integer, nullable=False, server_default='0')
    disable_trial_on_leave = Column(Boolean, nullable=False, server_default='true')
    disable_paid_on_leave = Column(Boolean, nullable=False, server_default='false')
    created_at = Column(AwareDateTime(), nullable=False, server_default=func.now())
    updated_at = Column(AwareDateTime(), nullable=True, onupdate=func.now())

    def __repr__(self) -> str:
        return f'<RequiredChannel id={self.id} channel_id={self.channel_id!r} active={self.is_active}>'


class UserChannelSubscription(Base):
    """Cache of user subscription status per required channel."""

    __tablename__ = 'user_channel_subscriptions'

    id = Column(Integer, primary_key=True, autoincrement=True)
    telegram_id = Column(BigInteger, nullable=False)
    channel_id = Column(String(100), nullable=False)  # matches RequiredChannel.channel_id
    is_member = Column(Boolean, nullable=False, server_default='false')
    checked_at = Column(AwareDateTime(), nullable=False, server_default=func.now())

    __table_args__ = (
        UniqueConstraint('telegram_id', 'channel_id', name='uq_user_channel_sub'),
        # UniqueConstraint creates its own index; only add telegram_id index for
        # "get all subs for user" queries
        Index('ix_user_channel_sub_telegram_id', 'telegram_id'),
        # Standalone channel_id index for delete_channel() bulk DELETE
        Index('ix_user_channel_sub_channel_id', 'channel_id'),
    )

    def __repr__(self) -> str:
        return (
            f'<UserChannelSubscription telegram_id={self.telegram_id}'
            f' channel={self.channel_id!r} member={self.is_member}>'
        )


# ── RBAC / ABAC models ──────────────────────────────────────────────────


class AdminRole(Base):
    """Role definition with permission groups for admin cabinet RBAC."""

    __tablename__ = 'admin_roles'

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), unique=True, nullable=False)
    description = Column(Text, nullable=True)
    level = Column(Integer, default=0, nullable=False)
    permissions = Column(JSONB, default=list, nullable=False)
    color = Column(String(7), nullable=True)
    icon = Column(String(50), nullable=True)
    is_system = Column(Boolean, default=False, nullable=False)
    is_active = Column(Boolean, default=True, nullable=False)
    created_by = Column(Integer, ForeignKey('users.id'), nullable=True)
    created_at = Column(AwareDateTime(), server_default=func.now())
    updated_at = Column(AwareDateTime(), server_default=func.now(), onupdate=func.now())

    creator = relationship('User', foreign_keys=[created_by])
    user_roles = relationship('UserRole', back_populates='role')

    def __repr__(self) -> str:
        return f'<AdminRole id={self.id} name={self.name!r} level={self.level}>'


class UserRole(Base):
    """M2M assignment of users to admin roles."""

    __tablename__ = 'user_roles'

    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
    role_id = Column(Integer, ForeignKey('admin_roles.id', ondelete='CASCADE'), nullable=False)
    assigned_by = Column(Integer, ForeignKey('users.id'), nullable=True)
    assigned_at = Column(AwareDateTime(), server_default=func.now())
    expires_at = Column(AwareDateTime(), nullable=True)
    is_active = Column(Boolean, default=True, nullable=False)

    __table_args__ = (UniqueConstraint('user_id', 'role_id', name='uq_user_role'),)

    user = relationship('User', foreign_keys=[user_id], back_populates='admin_roles_rel')
    role = relationship('AdminRole', back_populates='user_roles')
    assigner = relationship('User', foreign_keys=[assigned_by])

    def __repr__(self) -> str:
        return f'<UserRole id={self.id} user_id={self.user_id} role_id={self.role_id}>'


class AccessPolicy(Base):
    """ABAC attribute-based access policy."""

    __tablename__ = 'access_policies'

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(200), nullable=False)
    description = Column(Text, nullable=True)
    role_id = Column(Integer, ForeignKey('admin_roles.id', ondelete='CASCADE'), nullable=True)
    priority = Column(Integer, default=0, nullable=False)
    effect = Column(String(10), nullable=False)  # "allow" / "deny"
    conditions = Column(JSONB, default=dict, nullable=False)
    resource = Column(String(100), nullable=False)
    actions = Column(JSONB, default=list, nullable=False)
    is_active = Column(Boolean, default=True, nullable=False)
    created_by = Column(Integer, ForeignKey('users.id'), nullable=True)
    created_at = Column(AwareDateTime(), server_default=func.now())
    updated_at = Column(AwareDateTime(), server_default=func.now(), onupdate=func.now())

    role = relationship('AdminRole')
    creator = relationship('User', foreign_keys=[created_by])

    def __repr__(self) -> str:
        return f'<AccessPolicy id={self.id} name={self.name!r} effect={self.effect!r}>'


class AdminAuditLog(Base):
    """Immutable audit log for admin actions."""

    __tablename__ = 'admin_audit_log'

    id = Column(BigInteger, primary_key=True, autoincrement=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
    action = Column(String(100), nullable=False)
    resource_type = Column(String(50), nullable=True)
    resource_id = Column(String(100), nullable=True)
    details = Column(JSONB, nullable=True)
    ip_address = Column(String(45), nullable=True)
    user_agent = Column(Text, nullable=True)
    status = Column(String(20), nullable=False)
    request_method = Column(String(10), nullable=True)
    request_path = Column(Text, nullable=True)
    created_at = Column(AwareDateTime(), server_default=func.now())

    __table_args__ = (
        Index('ix_admin_audit_user_created', 'user_id', 'created_at'),
        Index('ix_admin_audit_resource', 'resource_type', 'resource_id'),
        Index('ix_admin_audit_created', 'created_at'),
    )

    user = relationship('User', foreign_keys=[user_id])

    def __repr__(self) -> str:
        return f'<AdminAuditLog id={self.id} action={self.action!r} status={self.status!r}>'
