"""initial_schema

Revision ID: c3a0e1c71ce6
Revises: 
Create Date: 2026-05-15 17:31:52.560351

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = 'c3a0e1c71ce6'
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    """Upgrade schema."""
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('mjk_app_releases',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('version', sa.String(length=20), nullable=False),
    sa.Column('release_date', sa.DateTime(timezone=True), nullable=False),
    sa.Column('notes', sa.Text(), nullable=False),
    sa.Column('mandatory', sa.Boolean(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_mjk_app_releases_version'), 'mjk_app_releases', ['version'], unique=True)
    op.create_table('mjk_broll_categories',
    sa.Column('slug', sa.String(length=128), nullable=False, comment='分类标识符，URL友好格式'),
    sa.Column('name', sa.String(length=256), nullable=False, comment='分类中文名称，三级分类直接对应 scene 标准化后的值'),
    sa.Column('parent_id', sa.BigInteger(), nullable=True, comment='父分类ID，NULL 表示根分类（一级）'),
    sa.Column('level', sa.BigInteger(), nullable=False, comment='层级：1=一级（大阶段），2=二级（工序），3=三级（场景）'),
    sa.Column('sort_order', sa.BigInteger(), nullable=False, comment='排序权重，装修流程有先后顺序'),
    sa.Column('status', sa.String(length=16), nullable=False, comment='状态：active（启用）/ disabled（停用）/ deleted（软删除）'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['parent_id'], ['mjk_broll_categories.id'], ),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('slug')
    )
    op.create_table('mjk_broll_tags',
    sa.Column('name', sa.String(length=64), nullable=False, comment='标签名称，如 近景、白天、水管'),
    sa.Column('category', sa.String(length=32), nullable=True, comment='标签维度：scene（场景）/ element（元素）/ style（风格）/ mood（情绪）/ time（时间）'),
    sa.Column('sort_order', sa.Integer(), nullable=False, comment='排序权重'),
    sa.Column('status', sa.String(length=16), nullable=False, comment='状态：active（启用）/ disabled（停用）'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('name')
    )
    op.create_table('mjk_cover_backgrounds',
    sa.Column('script_code', sa.String(length=64), nullable=False, comment='关联脚本大类 code，如 bk（装修避坑）'),
    sa.Column('title', sa.String(length=256), nullable=False, comment='背景图名称，运营识别用'),
    sa.Column('url', sa.String(length=1024), nullable=False, comment='七牛云 CDN 图片地址'),
    sa.Column('sort_order', sa.BigInteger(), nullable=False, comment='排序权重，数字越小越靠前'),
    sa.Column('status', sa.String(length=16), nullable=False, comment='状态：active（启用）/ disabled（停用）/ deleted（软删除）'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('mjk_point_batches',
    sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
    sa.Column('amount', sa.Integer(), nullable=False, comment='初始积分'),
    sa.Column('remaining', sa.Integer(), nullable=False, comment='剩余可用积分'),
    sa.Column('expired_at', sa.DateTime(timezone=True), nullable=False, comment='过期时间（created_at + 180 天）'),
    sa.Column('source', sa.String(length=32), nullable=False, comment='来源：wxpay / invite / gift / compensation'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('mjk_point_recharge_orders',
    sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
    sa.Column('points', sa.Integer(), nullable=False, comment='充值积分数'),
    sa.Column('amount_rmb', sa.Integer(), nullable=False, comment='人民币金额（单位：分，如 500 = 5 元）'),
    sa.Column('out_trade_no', sa.String(length=64), nullable=True, comment='商户订单号（传给微信的 out_trade_no）'),
    sa.Column('prepay_id', sa.String(length=64), nullable=True, comment='微信预支付会话标识（统一下单返回）'),
    sa.Column('wx_order_no', sa.String(length=64), nullable=True, comment='微信支付订单号（微信侧唯一标识）'),
    sa.Column('openid', sa.String(length=64), nullable=True, comment='用户微信 OpenID（统一下单必需）'),
    sa.Column('client_ip', sa.String(length=45), nullable=True, comment='用户下单时的 IP 地址'),
    sa.Column('trade_type', sa.String(length=16), nullable=True, comment='交易类型：JSAPI / NATIVE / APP'),
    sa.Column('status', sa.String(length=20), nullable=False, comment='订单状态：pending / paid / failed / closed'),
    sa.Column('paid_at', sa.DateTime(timezone=True), nullable=True, comment='支付成功时间'),
    sa.Column('closed_at', sa.DateTime(timezone=True), nullable=True, comment='订单关闭时间（超时未支付）'),
    sa.Column('request_params', sa.Text(), nullable=True, comment='统一下单请求参数（JSON 格式，用于排查请求侧问题）'),
    sa.Column('request_response', sa.Text(), nullable=True, comment='统一下单响应内容（JSON 格式，用于排查微信返回）'),
    sa.Column('notify_raw', sa.Text(), nullable=True, comment='微信回调原始内容（XML/JSON，用于排查回调问题）'),
    sa.Column('notify_verified', sa.Boolean(), nullable=False, comment='回调签名是否验证通过'),
    sa.Column('query_result', sa.Text(), nullable=True, comment='主动查询订单结果（JSON 格式，用于二次确认）'),
    sa.Column('error_code', sa.String(length=32), nullable=True, comment='错误码（微信返回或系统异常）'),
    sa.Column('error_msg', sa.Text(), nullable=True, comment='错误描述（用于快速定位问题）'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('out_trade_no')
    )
    op.create_table('mjk_point_transactions',
    sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
    sa.Column('type', sa.String(length=20), nullable=False, comment='变动类型：recharge / consume / expire / refund'),
    sa.Column('amount', sa.Integer(), nullable=False, comment='变动数量（正数）'),
    sa.Column('balance_before', sa.Integer(), nullable=False, comment='变动前总余额'),
    sa.Column('balance_after', sa.Integer(), nullable=False, comment='变动后总余额'),
    sa.Column('source_type', sa.String(length=32), nullable=True, comment='消费来源类型：script / polish / voice_clone / tts / video'),
    sa.Column('source_id', sa.String(length=64), nullable=True, comment='关联的任务 ID 或订单 ID'),
    sa.Column('batch_id', sa.BigInteger(), nullable=True, comment='关联的积分批次 ID（消费时记录从哪个批次扣）'),
    sa.Column('duration', sa.Float(), nullable=True, comment='时长（秒），按秒计费业务记录'),
    sa.Column('category', sa.String(length=32), nullable=True, comment='业务分类：脚本生成 / 配音合成 / 视频生成 / 压制成片 / 字幕烧录 / 封面设计 / 充值'),
    sa.Column('description', sa.Text(), nullable=True, comment='描述'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('mjk_user_devices',
    sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID（唯一约束，强制单设备登录）'),
    sa.Column('device_id', sa.String(length=64), nullable=False, comment='设备唯一标识（前端生成）'),
    sa.Column('device_name', sa.String(length=128), nullable=True, comment="设备名称（如 'MacBook Pro'）"),
    sa.Column('os_info', sa.String(length=128), nullable=True, comment='操作系统信息'),
    sa.Column('app_version', sa.String(length=32), nullable=True, comment='应用版本号'),
    sa.Column('refresh_token_hash', sa.String(length=64), nullable=True, comment='Refresh Token SHA256 哈希（用于校验和撤销）'),
    sa.Column('last_active_at', sa.DateTime(timezone=True), nullable=False, comment='最后活跃时间'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('user_id')
    )
    op.create_table('mjk_user_points',
    sa.Column('user_id', sa.UUID(), nullable=False, comment='用户 ID'),
    sa.Column('balance', sa.Integer(), nullable=False, comment='当前积分余额（允许欠费为负）'),
    sa.Column('total_recharged', sa.Integer(), nullable=False, comment='累计充值积分'),
    sa.Column('total_consumed', sa.Integer(), nullable=False, comment='累计消费积分'),
    sa.Column('total_expired', sa.Integer(), nullable=False, comment='累计过期积分'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('user_id')
    )
    op.create_table('mjk_users',
    sa.Column('mobile', sa.String(length=20), nullable=False, comment='手机号，登录账号'),
    sa.Column('password_hash', sa.String(length=255), nullable=True, comment='密码哈希（bcrypt），预留字段'),
    sa.Column('status', sa.String(length=20), nullable=False, comment='账号状态'),
    sa.Column('nickname', sa.String(length=64), nullable=True, comment='用户昵称'),
    sa.Column('avatar_url', sa.Text(), nullable=True, comment='头像 URL'),
    sa.Column('source', sa.String(length=32), nullable=False, comment='注册来源'),
    sa.Column('invited_by', sa.String(length=36), nullable=True, comment='邀请人 user_id'),
    sa.Column('last_login_at', sa.DateTime(timezone=True), nullable=True, comment='最后登录时间'),
    sa.Column('last_login_ip', sa.String(length=45), nullable=True, comment='最后登录 IP（IPv6 最大 45 字符）'),
    sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True, comment='注销时间（软删除标记）'),
    sa.Column('extra', postgresql.JSONB(astext_type=sa.Text()), nullable=False, comment='冗余字段，备用'),
    sa.Column('id', sa.UUID(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('mobile')
    )
    op.create_table('mjk_broll_materials',
    sa.Column('category_id', sa.BigInteger(), nullable=False, comment='所属三级分类ID，关联 mjk_broll_categories'),
    sa.Column('title', sa.String(length=256), nullable=False, comment='素材标题/文件名，运营后台识别用'),
    sa.Column('url', sa.String(length=1024), nullable=False, comment='七牛云 CDN 访问地址，FFmpeg合成和前端播放直接使用'),
    sa.Column('duration', sa.Float(), nullable=False, comment='视频时长（秒），FFmpeg probe 提取，入库时必须大于0'),
    sa.Column('usage_count', sa.BigInteger(), nullable=False, comment='累计使用次数，驱动加权随机算法'),
    sa.Column('status', sa.String(length=16), nullable=False, comment='状态：active（可用）/ disabled（下架）/ deleted（软删除）'),
    sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['category_id'], ['mjk_broll_categories.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('mjk_release_packages',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('release_id', sa.Integer(), nullable=False),
    sa.Column('platform', sa.String(length=20), nullable=False),
    sa.Column('architecture', sa.String(length=20), nullable=False),
    sa.Column('filename', sa.String(length=255), nullable=False),
    sa.Column('file_url', sa.String(length=500), nullable=False),
    sa.Column('file_size', sa.BigInteger(), nullable=False),
    sa.Column('signature', sa.Text(), nullable=False),
    sa.Column('download_count', sa.Integer(), nullable=False),
    sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
    sa.ForeignKeyConstraint(['release_id'], ['mjk_app_releases.id'], ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('release_id', 'platform', 'architecture', name='uix_pkg_platform_arch')
    )
    # ### end Alembic commands ###


def downgrade() -> None:
    """Downgrade schema."""
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('mjk_release_packages')
    op.drop_table('mjk_broll_materials')
    op.drop_table('mjk_users')
    op.drop_table('mjk_user_points')
    op.drop_table('mjk_user_devices')
    op.drop_table('mjk_point_transactions')
    op.drop_table('mjk_point_recharge_orders')
    op.drop_table('mjk_point_batches')
    op.drop_table('mjk_cover_backgrounds')
    op.drop_table('mjk_broll_tags')
    op.drop_table('mjk_broll_categories')
    op.drop_index(op.f('ix_mjk_app_releases_version'), table_name='mjk_app_releases')
    op.drop_table('mjk_app_releases')
    # ### end Alembic commands ###
