from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")

    APP_NAME: str = "VideoGen API"
    APP_VERSION: str = "1.0.0"
    DEBUG: bool = False
    SECRET_KEY: str = "change-me"

    DATABASE_URL: str = "sqlite+aiosqlite:///./videogen.db"

    REDIS_URL: str = ""  # Leave empty to disable Redis (rate limiting, captcha)

    JWT_ALGORITHM: str = "HS256"
    JWT_EXPIRE_MINUTES: int = 1440
    JWT_EXPIRE_REMEMBER_MINUTES: int = 10080

    SEEDANCE_API_KEY: str = ""
    SEEDANCE_API_BASE: str = "https://ark.cn-beijing.volces.com/api/v3"
    SEEDANCE_CALLBACK_URL: str = ""

    LLM_API_BASE: str = "https://api.openai.com/v1"
    LLM_API_KEY: str = ""
    LLM_MODEL: str = "gpt-4o"
    LLM_MOCK: bool = True

    ENCRYPTION_KEY: str = "changeme-32bytes-base64-key-here!!"

    # SMS settings
    # 兼容旧字段：SMS_API_URL/SMS_API_KEY/SMS_SIGN_NAME/SMS_TEMPLATE_CODE 保留，
    # 新接入默认使用火山引擎短信 SDK。
    SMS_API_URL: str = ""
    SMS_API_KEY: str = ""
    SMS_SIGN_NAME: str = "VideoGen"
    SMS_TEMPLATE_CODE: str = "SMS_001"
    SMS_MOCK: bool = True

    # 火山引擎短信配置。
    # SmsAccount=消息组ID，TemplateID=模板ID，Sign=短信签名内容。
    VOLC_SMS_ACCESS_KEY_ID: str = ""
    VOLC_SMS_SECRET_ACCESS_KEY: str = ""
    VOLC_SMS_ACCOUNT: str = ""
    VOLC_SMS_TEMPLATE_ID: str = ""
    VOLC_SMS_SIGN: str = ""
    SMS_CODE_LENGTH: int = 4
    SMS_CODE_TTL_SECONDS: int = 300
    SMS_SEND_INTERVAL_SECONDS: int = 60
    SMS_DAILY_LIMIT: int = 20

    # Payment settings
    WECHAT_MCH_ID: str = ""
    WECHAT_API_KEY: str = ""
    WECHAT_CERT_PATH: str = ""
    ALIPAY_APP_ID: str = ""
    ALIPAY_PRIVATE_KEY: str = ""
    ALIPAY_PUBLIC_KEY: str = ""
    ALIPAY_NOTIFY_URL: str = ""
    PAYMENT_MOCK: bool = False  # Default off; use admin panel to enable for testing

    STORAGE_TYPE: str = "local"
    STORAGE_LOCAL_PATH: str = "./storage/generate/videos"
    STORAGE_IMAGE_LOCAL_PATH: str = "./storage/generate/images"
    STORAGE_VIDEO_COVER_LOCAL_PATH: str = "./storage/generate/covers"
    UPLOAD_LOCAL_PATH: str = "./storage/uploads"

    # 本地视频封面截帧配置。
    # 说明：
    # - FFMPEG_BIN 为空时自动从系统 PATH 查找 ffmpeg / ffmpeg.exe。
    # - VIDEO_COVER_TIMEOUT_SECONDS 必须较短，避免 ffmpeg 异常卡住下载 worker。
    FFMPEG_BIN: str = ""
    VIDEO_COVER_SEEK_TIME: str = "00:00:01"
    VIDEO_COVER_FALLBACK_SEEK_TIME: str = "00:00:00"
    VIDEO_COVER_WIDTH: int = 720
    VIDEO_COVER_TIMEOUT_SECONDS: int = 15
    VIDEO_COVER_FORMAT: str = "jpg"

    CAPTCHA_ENABLED: bool = True

    BASE_URL: str = "http://ceshi.apiforeign.minzhong.cn"

    CORS_ORIGINS: list[str] = ["*"]

    RATE_LIMIT_ENABLED: bool = True

    # ChatAPI async generation pipeline settings
    CELERY_BROKER_URL: str = ""
    CELERY_RESULT_BACKEND: str = ""
    # Celery async 兼容配置。
    # single_loop：每个 Celery 子进程一个专用 event loop，推荐线上/本地统一使用。
    # direct：旧版线程本地 loop 降级模式，建议配合 CELERY_DB_USE_NULLPOOL=true。
    CELERY_ASYNC_RUNNER_MODE: str = "single_loop"
    CELERY_DB_USE_NULLPOOL: bool = False
    CELERY_STARTUP_RECOVERY_ENABLED: bool = True

    CHATAPI_REQUEST_TIMEOUT_SECONDS: int = 180
    CHATAPI_VIDEO_FPS: float = 0.5
    CHATAPI_ASYNC_MAX_RETRIES: int = 3
    CHATAPI_ASYNC_RETRY_BACKOFF_SECONDS: int = 30
    CHATAPI_ASYNC_POLL_INTERVAL_SECONDS: int = 30
    CHATAPI_ASYNC_IMAGE_DEADLINE_MINUTES: int = 10
    CHATAPI_ASYNC_VIDEO_DEADLINE_MINUTES: int = 30

    # Distributed provider concurrency limits. 0 means disabled/no-op.
    ARK_CHAT_PROMPT_MAX_CONCURRENCY: int = 20
    ARK_IMAGE_CREATE_MAX_CONCURRENCY: int = 10
    ARK_VIDEO_CREATE_MAX_CONCURRENCY: int = 10
    ARK_IMAGE_POLL_MAX_CONCURRENCY: int = 50
    ARK_VIDEO_POLL_MAX_CONCURRENCY: int = 50
    RESULT_DOWNLOAD_MAX_CONCURRENCY: int = 10
    PROVIDER_LIMIT_WAIT_TIMEOUT_SECONDS: float = 30.0
    PROVIDER_LIMIT_TOKEN_TTL_SECONDS: int = 600

    CELERY_DB_POOL_SIZE: int = 1
    CELERY_DB_MAX_OVERFLOW: int = 2
    CELERY_DB_POOL_TIMEOUT: int = 60
    CELERY_DB_POOL_RECYCLE: int = 1800

    # Celery 图片/视频下载容灾配置。
    # Redis broker 下 priority=0 最高，priority=9 最低。
    DOWNLOAD_TASK_PRIORITY_NORMAL: int = 5
    DOWNLOAD_TASK_PRIORITY_RECOVER: int = 0
    DOWNLOAD_TASK_MAX_ATTEMPTS: int = 3
    DOWNLOAD_TASK_RETRY_BACKOFF_SECONDS: int = 30
    DOWNLOAD_TASK_LEASE_SECONDS: int = 10 * 60
    DOWNLOAD_TASK_QUEUE_TIMEOUT_SECONDS: int = 5 * 60
    DOWNLOAD_RECOVERY_BATCH_SIZE: int = 20
    DOWNLOAD_RECOVERY_STARTUP_DELAY_SECONDS: int = 3
    # 下载恢复自循环：不依赖 Celery beat，不新增 worker；由 gen_result_download 队列周期扫描 DB/Redis。
    DOWNLOAD_RECOVERY_LOOP_ENABLED: bool = False
    DOWNLOAD_RECOVERY_INTERVAL_SECONDS: int = 60
    DOWNLOAD_RECOVERY_LOOP_LOCK_KEY: str = "vg:celery:download_recovery_loop_lock"
    DOWNLOAD_RECOVERY_LOOP_LOCK_TTL_SECONDS: int = 55
    DOWNLOAD_RETRY_COUNTDOWN_EXTRA_SECONDS: int = 1
    DOWNLOAD_NON_RETRYABLE_LOCAL_ERRORS: bool = True
    DOWNLOAD_EVENT_VERBOSE_ENABLED: bool = True
    MEDIA_TOKEN_SNAPSHOT_ENABLED: bool = True
    DOWNLOAD_ACTIVE_REDIS_HASH_KEY: str = "vg:celery:download:active"
    DOWNLOAD_ACTIVE_REDIS_ZSET_KEY: str = "vg:celery:download:active_index"

    # Celery 生成链路 / provider poll 容灾配置。
    # 说明：
    # - 启动容灾保留，但恢复扫描独立投递到 CELERY_RECOVERY_QUEUE。
    # - worker_ready 每个 worker 都会尝试抢启动恢复锁，只有抢到锁的 worker 投递恢复协调任务。
    # - poll active 使用独立 Redis key，避免影响稳定的下载 active 注册表。
    GENERATION_RECOVERY_BATCH_SIZE: int = 20
    GENERATION_RECOVERY_MAX_ROUNDS: int = 1
    POLL_RECOVERY_BATCH_SIZE: int = 20
    POLL_TASK_LEASE_SECONDS: int = 5 * 60
    POLL_TASK_QUEUE_TIMEOUT_SECONDS: int = 2 * 60
    POLL_ACTIVE_REDIS_HASH_KEY: str = "vg:celery:poll:active"
    POLL_ACTIVE_REDIS_ZSET_KEY: str = "vg:celery:poll:active_index"
    CELERY_STARTUP_RECOVERY_LOCK_KEY: str = "vg:celery:startup_recovery_lock"
    CELERY_STARTUP_RECOVERY_LOCK_TTL_SECONDS: int = 120
    CELERY_RECOVERY_QUEUE: str = "gen_recovery"
    CELERY_RECOVERY_TASK_LOCK_TTL_SECONDS: int = 10 * 60
    CELERY_RECOVERY_SOFT_TIME_LIMIT_SECONDS: int = 300
    CELERY_RECOVERY_TIME_LIMIT_SECONDS: int = 420
    CELERY_RECOVERY_STARTUP_TASK_LOCK_KEY: str = "vg:celery:startup_recovery_task_lock"
    GENERATION_RECOVERY_LOCK_KEY: str = "vg:celery:generation_recovery_lock"
    DOWNLOAD_RECOVERY_LOCK_KEY: str = "vg:celery:download_recovery_lock"
    MODULE_ASYNC_RECOVERY_LOCK_KEY: str = "vg:celery:module_async_recovery_lock"
    SHOT_SPLIT_RECOVERY_LOCK_KEY: str = "vg:celery:shot_split_recovery_lock"

    # 模块异步任务容灾配置。
    # 覆盖 ModuleGenerationStep 提词任务、shot 原视频/片段分析、shot ffmpeg 切割 active 注册。
    # 恢复扫描走 CELERY_RECOVERY_QUEUE，真实业务任务回到原始队列。
    MODULE_ASYNC_RECOVERY_BATCH_SIZE: int = 20
    MODULE_ASYNC_QUEUE_TIMEOUT_SECONDS: int = 5 * 60
    MODULE_ASYNC_LEASE_SECONDS: int = 10 * 60
    MODULE_ASYNC_LOCK_TTL_SECONDS: int = 10 * 60
    MODULE_ASYNC_REQUEUE_DELAY_SECONDS: int = 10
    MODULE_ASYNC_ACTIVE_REDIS_HASH_KEY: str = "vg:celery:module_async:active"
    MODULE_ASYNC_ACTIVE_REDIS_ZSET_KEY: str = "vg:celery:module_async:active_index"
    MODULE_ASYNC_LOCK_KEY_PREFIX: str = "vg:celery:module_async:lock"

    RESOURCE_SIGN_SECRET: str = "resource-signature-secret-key-for-API-authentication"
    RESOURCE_SIGN_EXPIRE_SECONDS: int = 60
    RESOURCE_SIGN_ARG_EXPIRE: str = "exp"
    RESOURCE_SIGN_ARG_SIGNATURE: str = "sign"

    # 爆款开头复刻默认配置。素材校验由前端完成，后端只接收已有上传接口返回的链接。
    HOT_OPENING_DEFAULT_VIDEO_DURATION: int = 4
    HOT_OPENING_DEFAULT_VIDEO_RATIO: str = "9:16"
    HOT_OPENING_DEFAULT_VIDEO_RESOLUTION: str = "480p"
    HOT_OPENING_DEFAULT_TARGET_PLATFORM: str = "抖音"

    # 拆镜复刻配置。
    # 原始上传视频和拆镜片段都属于 uploads 素材域；只有 generate 生成结果走 token 验签。
    SHOT_ANALYSIS_TIMEOUT_SECONDS: int = 180
    SHOT_ANALYSIS_TEMPERATURE: float = 0.1
    SHOT_ANALYSIS_MAX_TOKENS: int = 5000
    SHOT_ANALYSIS_VIDEO_FPS: float = 1.0
    SHOT_ANALYSIS_MAX_LOCAL_VIDEO_MB: int = 45

    SHOT_SEGMENT_LOCAL_PATH: str = "./storage/uploads/shot_segments"
    SHOT_SEGMENT_URL_PREFIX: str = "/uploads/shot_segments"
    SHOT_SPLIT_MIN_SECONDS: float = 2
    SHOT_SPLIT_MAX_SECONDS: float = 15
    SHOT_SPLIT_END_TOLERANCE_SECONDS: float = 0.5
    SHOT_DURATION_TOLERANCE_SECONDS: float = 1.0
    SHOT_FFMPEG_TIMEOUT_SECONDS: int = 120
    SHOT_FFPROBE_TIMEOUT_SECONDS: int = 20
    FFPROBE_BIN: str = ""

    # 继续复用 gen_result_download 队列，但限制 ffmpeg 并发，避免拖慢 Chat 下载。
    SHOT_SPLIT_MAX_CONCURRENT: int = 1
    SHOT_SPLIT_MAX_RETRY_COUNT: int = 3
    SHOT_SPLIT_RETRY_BACKOFF_SECONDS: int = 30
    SHOT_SPLIT_LEASE_SECONDS: int = 10 * 60
    SHOT_SPLIT_PENDING_TIMEOUT_SECONDS: int = 5 * 60
    SHOT_SPLIT_RECOVERY_BATCH_SIZE: int = 20
    SHOT_SPLIT_LOCK_KEY_PREFIX: str = "vg:shot_replicate:split:lock"
    SHOT_SPLIT_SEMAPHORE_KEY_PREFIX: str = "vg:shot_replicate:split:semaphore"

    SHOT_REPLICATE_DEFAULT_VIDEO_DURATION: int = 4
    SHOT_REPLICATE_DEFAULT_VIDEO_RATIO: str = "9:16"
    SHOT_REPLICATE_DEFAULT_VIDEO_RESOLUTION: str = "480p"
    SHOT_REPLICATE_DEFAULT_TARGET_PLATFORM: str = "抖音"


settings = Settings()
