# SafeCheckIn Cloud Server v1.0 - Config File Support + Admin Panel + Daily Reminder

from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks, Form, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, EmailStr
from typing import Optional
import sqlite3
import hashlib
import secrets
import smtplib
import asyncio
import json
import os
from datetime import datetime, timedelta
from email.message import EmailMessage
import uvicorn
from contextlib import asynccontextmanager

# ========== 配置文件加载 ==========
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")

def load_config_from_file():
    """从 config.json 读取 SMTP 配置"""
    if os.path.exists(CONFIG_FILE):
        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                cfg = json.load(f)
            required = ["smtp_host", "smtp_username", "smtp_password"]
            if all(k in cfg and cfg[k] for k in required):
                print(f"[Config] 已从 config.json 加载 SMTP 配置 (发件人: {cfg['smtp_username']})")
                return cfg
            else:
                missing = [k for k in required if k not in cfg or not cfg[k]]
                print(f"[Config] config.json 存在但字段不完整，缺少: {missing}")
        except Exception as e:
            print(f"[Config] 读取 config.json 失败: {e}")
    return None

# ========== 管理员会话 ==========
admin_sessions = {}

ADMIN_USERNAME = "root"
ADMIN_PASSWORD = "123456"

# ========== 数据模型 ==========
class UserRegister(BaseModel):
    email: EmailStr
    password: str

class UserLogin(BaseModel):
    email: EmailStr
    password: str

class VerifyCode(BaseModel):
    email: EmailStr
    code: str

class ForgotPassword(BaseModel):
    email: EmailStr

class ResetPassword(BaseModel):
    email: EmailStr
    code: str
    new_password: str

class CheckInRequest(BaseModel):
    user_id: int
    note: Optional[str] = None

class CheckInSyncRequest(BaseModel):
    user_id: int
    consecutive_days: int
    note: Optional[str] = None

class ContactRequest(BaseModel):
    user_id: int
    name: str
    email: EmailStr
    phone: Optional[str] = ""

class ContactUpdate(BaseModel):
    id: int
    name: str
    email: EmailStr
    phone: Optional[str] = ""

class GracePeriodUpdate(BaseModel):
    user_id: int
    hours: int
    minutes: int

class PresetMessageUpdate(BaseModel):
    user_id: int
    message: str

class SMTPConfig(BaseModel):
    smtp_host: str
    smtp_port: int = 587
    smtp_username: str
    smtp_password: str = ""
    smtp_security: str = "TLS"

class DailyReminderUpdate(BaseModel):
    user_id: int
    enabled: bool

class AdminLoginModel(BaseModel):
    username: str
    password: str

class AdminPasswordReset(BaseModel):
    user_id: int
    new_password: str

# ========== 数据库初始化 ==========
def init_db():
    conn = sqlite3.connect('safecheckin.db', check_same_thread=False)
    c = conn.cursor()
    
    # Users table
    c.execute('''CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        email TEXT UNIQUE NOT NULL,
        password_hash TEXT NOT NULL,
        verification_code TEXT,
        code_expiry TIMESTAMP,
        is_verified INTEGER DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        grace_period_hours INTEGER DEFAULT 24,
        grace_period_minutes INTEGER DEFAULT 0,
        preset_message TEXT DEFAULT "如果你收到这条消息，说明我可能出事了，请尽快联系我或报警。",
        last_check_in TIMESTAMP,
        consecutive_days INTEGER DEFAULT 0,
        check_in_count INTEGER DEFAULT 0,
        daily_reminder INTEGER DEFAULT 1
    )''')
    
    # 检查并添加缺失的字段
    try:
        c.execute("SELECT check_in_count FROM users LIMIT 1")
    except sqlite3.OperationalError:
        print("Adding missing column: check_in_count")
        c.execute("ALTER TABLE users ADD COLUMN check_in_count INTEGER DEFAULT 0")
    
    try:
        c.execute("SELECT daily_reminder FROM users LIMIT 1")
    except sqlite3.OperationalError:
        print("Adding missing column: daily_reminder")
        c.execute("ALTER TABLE users ADD COLUMN daily_reminder INTEGER DEFAULT 1")
    
    # Contacts table
    c.execute('''CREATE TABLE IF NOT EXISTS contacts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        name TEXT NOT NULL,
        email TEXT NOT NULL,
        phone TEXT
    )''')
    
    # Check-ins table
    c.execute('''CREATE TABLE IF NOT EXISTS check_ins (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        note TEXT
    )''')
    
    # Server SMTP config
    c.execute('''CREATE TABLE IF NOT EXISTS server_config (
        id INTEGER PRIMARY KEY,
        smtp_host TEXT,
        smtp_port INTEGER DEFAULT 587,
        smtp_username TEXT,
        smtp_password TEXT,
        smtp_security TEXT DEFAULT "TLS",
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )''')
    
    # Admins table
    c.execute('''CREATE TABLE IF NOT EXISTS admins (
        id INTEGER PRIMARY KEY,
        username TEXT UNIQUE,
        password_hash TEXT
    )''')
    
    # 创建/更新默认管理员 (密码: 123456)
    c.execute("SELECT id FROM admins WHERE username=?", (ADMIN_USERNAME,))
    row = c.fetchone()
    pwd_hash = hashlib.sha256(ADMIN_PASSWORD.encode()).hexdigest()
    if row:
        c.execute("UPDATE admins SET password_hash=? WHERE username=?", (pwd_hash, ADMIN_USERNAME))
    else:
        c.execute("INSERT INTO admins (id, username, password_hash) VALUES (1, ?, ?)", (ADMIN_USERNAME, pwd_hash))
    
    conn.commit()
    
    # 启动时从 config.json 同步配置到数据库
    file_config = load_config_from_file()
    if file_config:
        c = conn.cursor()
        c.execute('SELECT id FROM server_config WHERE id=1')
        if c.fetchone():
            c.execute('''UPDATE server_config SET smtp_host=?, smtp_port=?, smtp_username=?, smtp_password=?, smtp_security=?, updated_at=? WHERE id=1''',
                      (file_config['smtp_host'], file_config.get('smtp_port', 587), file_config['smtp_username'],
                       file_config['smtp_password'], file_config.get('smtp_security', 'TLS'), datetime.now()))
        else:
            c.execute('''INSERT INTO server_config (id, smtp_host, smtp_port, smtp_username, smtp_password, smtp_security, updated_at) VALUES (1,?,?,?,?,?,?)''',
                      (file_config['smtp_host'], file_config.get('smtp_port', 587), file_config['smtp_username'],
                       file_config['smtp_password'], file_config.get('smtp_security', 'TLS'), datetime.now()))
        conn.commit()
        print("[Config] SMTP 配置已同步到数据库")
    
    conn.close()

def get_db():
    conn = sqlite3.connect('safecheckin.db', check_same_thread=False)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
    finally:
        conn.close()

def hash_password(password: str) -> str:
    return hashlib.sha256(password.encode()).hexdigest()

def generate_code() -> str:
    return secrets.token_hex(3).upper()[:6]

def get_smtp_config(conn):
    c = conn.cursor()
    c.execute('SELECT * FROM server_config WHERE id=1')
    r = c.fetchone()
    return dict(r) if r else None

def send_email(to_email, subject, body, config):
    try:
        msg = EmailMessage()
        msg.set_content(body, charset="utf-8")
        msg["From"] = config['smtp_username']
        msg["To"] = to_email
        msg["Subject"] = subject

        security = config.get('smtp_security', 'TLS')
        host = config['smtp_host']
        port = int(config['smtp_port'])

        if security == 'SSL':
            server = smtplib.SMTP_SSL(host, port, timeout=30)
        else:
            server = smtplib.SMTP(host, port, timeout=30)
            if security == 'TLS':
                server.starttls()

        server.login(config['smtp_username'], config['smtp_password'])
        server.send_message(msg)
        server.quit()
        return True
    except Exception as e:
        print(f"Email error: {e}")
        import traceback
        traceback.print_exc()
        return False

# ========== 后台任务：每日12点提醒打卡 ==========
async def daily_reminder_task():
    """每天12:00给开启提醒的用户发送打卡提醒邮件"""
    while True:
        now = datetime.now()
        # 计算到下一个12:00的秒数
        target = now.replace(hour=12, minute=0, second=0, microsecond=0)
        if now >= target:
            target = target + timedelta(days=1)
        wait_seconds = (target - now).total_seconds()
        print(f"[Reminder] 下次每日提醒将在 {target.strftime('%Y-%m-%d %H:%M:%S')} 发送，等待 {int(wait_seconds)} 秒")
        await asyncio.sleep(wait_seconds)
        
        try:
            conn = sqlite3.connect('safecheckin.db', check_same_thread=False)
            conn.row_factory = sqlite3.Row
            config = get_smtp_config(conn)
            
            if config:
                c = conn.cursor()
                c.execute('SELECT email FROM users WHERE is_verified=1 AND daily_reminder=1')
                users = c.fetchall()
                
                sent = 0
                for user in users:
                    if send_email(user['email'], "【SafeCheckIn】每日打卡提醒", 
                                 f"您好，今天是{datetime.now().strftime('%Y年%m月%d日')}，别忘了打卡报平安！\n\n打开 APP 完成打卡即可。", config):
                        sent += 1
                
                print(f"[Reminder] 每日提醒已发送: {sent}/{len(users)} 封邮件")
            
            conn.close()
        except Exception as e:
            print(f"[Reminder] 每日提醒发送失败: {e}")

# ========== 后台任务：检查超时用户 ==========
async def check_overdue_users():
    while True:
        try:
            conn = sqlite3.connect('safecheckin.db', check_same_thread=False)
            conn.row_factory = sqlite3.Row
            config = get_smtp_config(conn)

            if config:
                c = conn.cursor()
                c.execute('SELECT * FROM users WHERE is_verified=1')

                for user in c.fetchall():
                    grace_ms = (user['grace_period_hours'] * 60 + user['grace_period_minutes']) * 60 * 1000
                    last_time = datetime.fromisoformat(user['last_check_in']) if user['last_check_in'] else datetime.now() - timedelta(days=2)

                    if datetime.now() - last_time > timedelta(milliseconds=grace_ms):
                        c.execute('SELECT * FROM contacts WHERE user_id=?', (user['id'],))
                        contacts = c.fetchall()

                        for contact in contacts:
                            send_email(
                                contact['email'],
                                "【紧急】死了吗-预警",
                                user['preset_message'],
                                config
                            )

            conn.close()
        except Exception as e:
            print(f"Check overdue error: {e}")

        await asyncio.sleep(60)

# ========== 生命周期管理 ==========
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup
    print("Starting SafeCheckIn Cloud Server v1.0...")
    init_db()
    asyncio.create_task(check_overdue_users())
    asyncio.create_task(daily_reminder_task())
    yield
    # Shutdown
    print("Shutting down SafeCheckIn server...")

app = FastAPI(title="SafeCheckIn", version="1.0", lifespan=lifespan)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])

# ========== 认证相关API ==========
@app.post("/api/auth/register")
def register(user: UserRegister, background_tasks: BackgroundTasks, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id FROM users WHERE email=?', (user.email,))
    if c.fetchone():
        raise HTTPException(status_code=400, detail="邮箱已注册")
    
    code = generate_code()
    expiry = datetime.now() + timedelta(minutes=10)
    c.execute('INSERT INTO users (email, password_hash, verification_code, code_expiry) VALUES (?, ?, ?, ?)',
              (user.email, hash_password(user.password), code, expiry))
    conn.commit()
    
    config = get_smtp_config(conn)
    if config:
        background_tasks.add_task(send_email, user.email, "死了吗-注册验证码", f"验证码: {code} (10分钟有效)", config)
    
    return {"message": "验证码已发送"}

@app.post("/api/auth/verify")
def verify_code(data: VerifyCode, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id, verification_code, code_expiry, is_verified FROM users WHERE email=?', (data.email,))
    row = c.fetchone()
    
    if not row:
        raise HTTPException(status_code=404, detail="用户不存在")
    
    if row['is_verified']:
        raise HTTPException(status_code=400, detail="用户已验证")
    
    if row['verification_code'] != data.code:
        raise HTTPException(status_code=400, detail="验证码错误")
    
    c.execute('UPDATE users SET is_verified=1 WHERE id=?', (row['id'],))
    conn.commit()
    
    return {"message": "验证成功", "user_id": row['id']}

@app.post("/api/auth/login")
def login(user: UserLogin, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id, email, is_verified, check_in_count FROM users WHERE email=? AND password_hash=?',
              (user.email, hash_password(user.password)))
    row = c.fetchone()
    
    if not row:
        raise HTTPException(status_code=401, detail="邮箱或密码错误")
    
    if not row['is_verified']:
        raise HTTPException(status_code=403, detail="请先验证邮箱")
    
    return {
        "message": "登录成功",
        "user_id": row['id'],
        "email": row['email'],
        "check_in_count": row['check_in_count']
    }

@app.post("/api/auth/forgot-password")
def forgot_password(data: ForgotPassword, background_tasks: BackgroundTasks, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id, is_verified FROM users WHERE email=?', (data.email,))
    row = c.fetchone()
    
    if not row:
        return {"message": "如存在会收到邮件"}
    
    if not row['is_verified']:
        raise HTTPException(status_code=400, detail="账号未验证")
    
    code = generate_code()
    c.execute('UPDATE users SET verification_code=?, code_expiry=? WHERE id=?',
              (code, datetime.now() + timedelta(minutes=30), row['id']))
    conn.commit()
    
    config = get_smtp_config(conn)
    if config:
        background_tasks.add_task(send_email, data.email, "死了吗-密码重置", f"验证码: {code} (30分钟有效)", config)
    
    return {"message": "已发送"}

@app.post("/api/auth/reset-password")
def reset_password(data: ResetPassword, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id, verification_code FROM users WHERE email=?', (data.email,))
    row = c.fetchone()
    
    if not row:
        raise HTTPException(status_code=404, detail="用户不存在")
    
    if row['verification_code'] != data.code:
        raise HTTPException(status_code=400, detail="验证码错误")
    
    c.execute('UPDATE users SET password_hash=?, verification_code=NULL WHERE id=?',
              (hash_password(data.new_password), row['id']))
    conn.commit()
    
    return {"message": "密码已重置"}

# ========== 账号注销API ==========
@app.delete("/api/account/{user_id}")
def delete_account(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    # 检查用户是否存在
    c.execute('SELECT id FROM users WHERE id=?', (user_id,))
    if not c.fetchone():
        raise HTTPException(status_code=404, detail="用户不存在")
    # 删除用户的联系人
    c.execute('DELETE FROM contacts WHERE user_id=?', (user_id,))
    # 删除用户的打卡记录
    c.execute('DELETE FROM check_ins WHERE user_id=?', (user_id,))
    # 删除用户
    c.execute('DELETE FROM users WHERE id=?', (user_id,))
    conn.commit()
    return {"message": "账号已注销"}

# ========== 打卡相关API ==========
@app.post("/api/checkin")
def check_in(data: CheckInRequest, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('INSERT INTO check_ins (user_id, timestamp, note) VALUES (?, datetime("now"), ?)',
              (data.user_id, data.note))
    c.execute('UPDATE users SET last_check_in=datetime("now"), check_in_count=check_in_count+1 WHERE id=?',
              (data.user_id,))
    conn.commit()
    return {"message": "打卡成功"}

@app.post("/api/checkin/sync")
def sync_check_in(data: CheckInSyncRequest, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('INSERT INTO check_ins (user_id, timestamp, note) VALUES (?, datetime("now"), ?)',
              (data.user_id, data.note))
    c.execute('UPDATE users SET last_check_in=datetime("now"), check_in_count=?, consecutive_days=? WHERE id=?',
              (data.consecutive_days, data.consecutive_days, data.user_id))
    conn.commit()
    return {"message": "同步成功", "check_in_count": data.consecutive_days}

@app.get("/api/checkin/{user_id}")
def get_check_ins(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT * FROM check_ins WHERE user_id=? ORDER BY timestamp DESC', (user_id,))
    check_ins = [dict(row) for row in c.fetchall()]
    return {"check_ins": check_ins}

@app.get("/api/checkin/{user_id}/count")
def get_check_in_count(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT check_in_count FROM users WHERE id=?', (user_id,))
    row = c.fetchone()
    if row:
        return {"check_in_count": row['check_in_count']}
    raise HTTPException(status_code=404, detail="用户不存在")

# ========== 用户信息API ==========
@app.get("/api/user/{user_id}")
def get_user_info(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT * FROM users WHERE id=?', (user_id,))
    row = c.fetchone()
    if row:
        return dict(row)
    raise HTTPException(status_code=404, detail="用户不存在")

@app.get("/api/user/stats/{user_id}")
def get_user_stats(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('''
        SELECT 
            check_in_count,
            consecutive_days,
            last_check_in,
            grace_period_hours,
            grace_period_minutes,
            preset_message,
            daily_reminder
        FROM users WHERE id = ?
    ''', (user_id,))
    row = c.fetchone()
    if row:
        return {
            "check_in_count": row['check_in_count'],
            "consecutive_days": row['consecutive_days'],
            "last_check_in": row['last_check_in'],
            "grace_period_hours": row['grace_period_hours'],
            "grace_period_minutes": row['grace_period_minutes'],
            "preset_message": row['preset_message'],
            "daily_reminder": bool(row['daily_reminder'])
        }
    raise HTTPException(status_code=404, detail="用户不存在")

# ========== 联系人相关API ==========
@app.post("/api/contacts")
def create_contact(contact: ContactRequest, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('INSERT INTO contacts (user_id, name, email, phone) VALUES (?, ?, ?, ?)',
              (contact.user_id, contact.name, contact.email, contact.phone))
    conn.commit()
    return {"message": "添加成功", "id": c.lastrowid}

@app.get("/api/contacts/{user_id}")
def get_contacts(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT * FROM contacts WHERE user_id=?', (user_id,))
    contacts = [dict(row) for row in c.fetchall()]
    return {"contacts": contacts}

@app.put("/api/contacts/{contact_id}")
def update_contact(contact_id: int, data: ContactUpdate, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('UPDATE contacts SET name=?, email=?, phone=? WHERE id=?',
              (data.name, data.email, data.phone, contact_id))
    conn.commit()
    return {"message": "联系人已更新"}

@app.delete("/api/contacts/{contact_id}")
def delete_contact(contact_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('DELETE FROM contacts WHERE id=?', (contact_id,))
    conn.commit()
    return {"message": "已删除"}

# ========== 设置相关API ==========
@app.post("/api/grace-period")
def update_grace_period(data: GracePeriodUpdate, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('UPDATE users SET grace_period_hours=?, grace_period_minutes=? WHERE id=?',
              (data.hours, data.minutes, data.user_id))
    conn.commit()
    return {"message": "已更新"}

@app.get("/api/grace-period/{user_id}")
def get_grace_period(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT grace_period_hours, grace_period_minutes FROM users WHERE id=?', (user_id,))
    row = c.fetchone()
    if row:
        return {"hours": row['grace_period_hours'], "minutes": row['grace_period_minutes']}
    raise HTTPException(status_code=404, detail="用户不存在")

@app.post("/api/preset-message")
def update_preset_message(data: PresetMessageUpdate, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('UPDATE users SET preset_message=? WHERE id=?', (data.message, data.user_id))
    conn.commit()
    return {"message": "预设消息已更新"}

@app.get("/api/preset-message/{user_id}")
def get_preset_message(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT preset_message FROM users WHERE id=?', (user_id,))
    row = c.fetchone()
    if row:
        return {"preset_message": row['preset_message']}
    raise HTTPException(status_code=404, detail="用户不存在")

# ========== 每日提醒API ==========
@app.post("/api/daily-reminder")
def update_daily_reminder(data: DailyReminderUpdate, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('UPDATE users SET daily_reminder=? WHERE id=?', (1 if data.enabled else 0, data.user_id))
    conn.commit()
    return {"message": "已更新", "daily_reminder": data.enabled}

@app.get("/api/daily-reminder/{user_id}")
def get_daily_reminder(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT daily_reminder FROM users WHERE id=?', (user_id,))
    row = c.fetchone()
    if row:
        return {"daily_reminder": bool(row['daily_reminder'])}
    raise HTTPException(status_code=404, detail="用户不存在")

# ========== SMTP配置API ==========
@app.get("/api/smtp-config")
def get_smtp_config_api(conn=Depends(get_db)):
    config = get_smtp_config(conn)
    if config:
        config['smtp_password'] = '******'
    return config or {"smtp_host": "", "smtp_port": 587, "smtp_username": ""}

@app.post("/api/smtp-config")
def update_smtp_config(config: SMTPConfig, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id FROM server_config WHERE id=1')
    if c.fetchone():
        c.execute('''UPDATE server_config SET smtp_host=?, smtp_port=?, smtp_username=?, smtp_password=?, smtp_security=?, updated_at=? WHERE id=1''',
                  (config.smtp_host, config.smtp_port, config.smtp_username, config.smtp_password, config.smtp_security, datetime.now()))
    else:
        c.execute('''INSERT INTO server_config (id, smtp_host, smtp_port, smtp_username, smtp_password, smtp_security, updated_at) VALUES (1,?,?,?,?,?,?)''',
                  (config.smtp_host, config.smtp_port, config.smtp_username, config.smtp_password, config.smtp_security, datetime.now()))
    conn.commit()
    return {"message": "SMTP配置已更新"}

# ========== 管理员API ==========
@app.post("/api/admin/login")
def admin_login(data: AdminLoginModel, conn=Depends(get_db)):
    if data.username != ADMIN_USERNAME or data.password != ADMIN_PASSWORD:
        raise HTTPException(status_code=401, detail="管理员账号或密码错误")
    token = secrets.token_hex(32)
    admin_sessions[token] = {"username": ADMIN_USERNAME, "created": datetime.now().isoformat()}
    return {"message": "登录成功", "token": token}

@app.get("/api/admin/users")
def admin_get_users(conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id, email, is_verified, created_at, last_check_in, check_in_count, consecutive_days, grace_period_hours, grace_period_minutes, daily_reminder, preset_message FROM users')
    users = [dict(row) for row in c.fetchall()]
    return {"users": users}

@app.delete("/api/admin/users/{user_id}")
def admin_delete_user(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id FROM users WHERE id=?', (user_id,))
    if not c.fetchone():
        raise HTTPException(status_code=404, detail="用户不存在")
    c.execute('DELETE FROM contacts WHERE user_id=?', (user_id,))
    c.execute('DELETE FROM check_ins WHERE user_id=?', (user_id,))
    c.execute('DELETE FROM users WHERE id=?', (user_id,))
    conn.commit()
    return {"message": "用户已删除"}

@app.get("/api/admin/user/{user_id}")
def admin_get_user_detail(user_id: int, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id, email, is_verified, created_at, last_check_in, check_in_count, consecutive_days, grace_period_hours, grace_period_minutes, daily_reminder, preset_message, password_hash FROM users WHERE id=?', (user_id,))
    row = c.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="用户不存在")
    # 获取联系人
    c.execute('SELECT * FROM contacts WHERE user_id=?', (user_id,))
    contacts = [dict(r) for r in c.fetchall()]
    # 获取最近打卡记录
    c.execute('SELECT * FROM check_ins WHERE user_id=? ORDER BY timestamp DESC LIMIT 20', (user_id,))
    checkins = [dict(r) for r in c.fetchall()]
    user = dict(row)
    user['contacts'] = contacts
    user['recent_checkins'] = checkins
    return user

@app.post("/api/admin/reset-password")
def admin_reset_password(data: AdminPasswordReset, conn=Depends(get_db)):
    c = conn.cursor()
    c.execute('SELECT id FROM users WHERE id=?', (data.user_id,))
    if not c.fetchone():
        raise HTTPException(status_code=404, detail="用户不存在")
    c.execute('UPDATE users SET password_hash=? WHERE id=?', (hash_password(data.new_password), data.user_id))
    conn.commit()
    return {"message": "用户密码已重置"}

# ========== 管理后台网页 ==========
@app.get("/admin", response_class=HTMLResponse)
def admin_page():
    return ADMIN_HTML

ADMIN_HTML = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SafeCheckIn 管理后台</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f0f2f5;color:#333}
.login-wrap{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)}
.login-box{background:#fff;border-radius:12px;padding:40px;width:360px;box-shadow:0 20px 60px rgba(0,0,0,0.3)}
.login-box h2{text-align:center;margin-bottom:8px;font-size:22px}
.login-box p{text-align:center;color:#999;margin-bottom:24px;font-size:13px}
.login-box input{width:100%;padding:12px 14px;border:1px solid #ddd;border-radius:8px;font-size:14px;margin-bottom:12px;transition:border-color .2s}
.login-box input:focus{outline:none;border-color:#667eea}
.login-box button{width:100%;padding:12px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:8px;font-size:15px;cursor:pointer;transition:opacity .2s}
.login-box button:hover{opacity:0.9}
.error-msg{color:#e74c3c;font-size:13px;text-align:center;margin-top:8px;display:none}

/* 主布局 */
.header{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:16px 24px;display:flex;justify-content:space-between;align-items:center;box-shadow:0 2px 8px rgba(0,0,0,0.15)}
.header h1{font-size:18px;font-weight:600}
.header .user-info{font-size:13px;opacity:0.9}
.header button{background:rgba(255,255,255,0.2);border:none;color:#fff;padding:6px 16px;border-radius:6px;cursor:pointer;font-size:13px}
.header button:hover{background:rgba(255,255,255,0.3)}

.container{max-width:1200px;margin:20px auto;padding:0 20px}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px}
.stat-card{background:#fff;border-radius:10px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.1)}
.stat-card .num{font-size:28px;font-weight:700;color:#667eea}
.stat-card .label{font-size:13px;color:#999;margin-top:4px}

.card{background:#fff;border-radius:10px;box-shadow:0 1px 3px rgba(0,0,0,0.1);margin-bottom:20px;overflow:hidden}
.card-header{padding:16px 20px;border-bottom:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center}
.card-header h3{font-size:15px;font-weight:600}
.card-body{padding:0}

table{width:100%;border-collapse:collapse}
th,td{padding:12px 16px;text-align:left;border-bottom:1px solid #f5f5f5;font-size:13px}
th{background:#fafafa;font-weight:600;color:#666;font-size:12px;text-transform:uppercase}
tr:hover{background:#fafafa}
td .actions{display:flex;gap:6px}

.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:500}
.badge-green{background:#e8f5e9;color:#2e7d32}
.badge-gray{background:#f5f5f5;color:#999}
.badge-blue{background:#e3f2fd;color:#1565c0}

.btn{padding:6px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:all .2s}
.btn-sm{padding:4px 10px;font-size:11px}
.btn-primary{background:#667eea;color:#fff}
.btn-primary:hover{background:#5a6fd6}
.btn-danger{background:#ffebee;color:#c62828}
.btn-danger:hover{background:#ffcdd2}
.btn-warning{background:#fff3e0;color:#e65100}
.btn-warning:hover{background:#ffe0b2}
.btn-success{background:#e8f5e9;color:#2e7d32}
.btn-success:hover{background:#c8e6c9}

/* Modal */
.modal-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:1000;justify-content:center;align-items:center}
.modal-overlay.active{display:flex}
.modal{background:#fff;border-radius:12px;width:500px;max-width:90vw;max-height:80vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.3)}
.modal-header{padding:16px 20px;border-bottom:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center}
.modal-header h3{font-size:16px}
.modal-close{background:none;border:none;font-size:20px;cursor:pointer;color:#999;padding:4px 8px}
.modal-close:hover{color:#333}
.modal-body{padding:20px}
.modal-footer{padding:16px 20px;border-top:1px solid #f0f0f0;display:flex;justify-content:flex-end;gap:8px}

.form-group{margin-bottom:16px}
.form-group label{display:block;font-size:13px;font-weight:500;margin-bottom:6px;color:#555}
.form-group input,.form-group textarea,.form-group select{width:100%;padding:10px 12px;border:1px solid #ddd;border-radius:8px;font-size:13px}
.form-group textarea{resize:vertical;min-height:60px;font-family:inherit}
.form-group input:focus,.form-group textarea:focus{outline:none;border-color:#667eea}

.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.detail-item{padding:10px;background:#f9fafb;border-radius:6px}
.detail-item .dl{font-size:11px;color:#999;margin-bottom:2px}
.detail-item .dv{font-size:14px;font-weight:500}

.sub-section{margin-top:20px}
.sub-section h4{font-size:14px;font-weight:600;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid #f0f0f0}

.empty{text-align:center;padding:40px;color:#999}
.empty svg{width:48px;height:48px;margin-bottom:12px;opacity:0.3}

.search-box{padding:10px 14px;border:1px solid #ddd;border-radius:8px;font-size:13px;width:250px}
.search-box:focus{outline:none;border-color:#667eea}

.toast{position:fixed;top:20px;right:20px;padding:12px 20px;border-radius:8px;color:#fff;font-size:13px;z-index:2000;transform:translateX(120%);transition:transform .3s;box-shadow:0 4px 12px rgba(0,0,0,0.2)}
.toast.show{transform:translateX(0)}
.toast-success{background:#2e7d32}
.toast-error{background:#c62828}

@media(max-width:768px){
  .container{padding:0 12px}
  .stats{grid-template-columns:1fr 1fr}
  th,td{padding:8px 10px;font-size:12px}
  .detail-grid{grid-template-columns:1fr}
  .search-box{width:100%}
}
</style>
</head>
<body>

<!-- 登录页 -->
<div class="login-wrap" id="loginPage">
  <div class="login-box">
    <h2>🔐 SafeCheckIn</h2>
    <p>管理后台</p>
    <input type="text" id="loginUser" placeholder="管理员账号" autocomplete="off">
    <input type="password" id="loginPass" placeholder="管理员密码" autocomplete="off">
    <button onclick="doLogin()">登 录</button>
    <div class="error-msg" id="loginError"></div>
  </div>
</div>

<!-- 主界面 -->
<div id="mainPage" style="display:none">
  <div class="header">
    <h1>🔐 SafeCheckIn 管理后台</h1>
    <div style="display:flex;align-items:center;gap:12px">
      <span class="user-info" id="adminName"></span>
      <button onclick="doLogout()">退出</button>
    </div>
  </div>
  <div class="container">
    <div class="stats" id="statsArea"></div>
    <div class="card">
      <div class="card-header">
        <h3>📋 用户列表</h3>
        <input class="search-box" type="text" placeholder="搜索邮箱..." id="searchInput" oninput="filterUsers()">
      </div>
      <div class="card-body">
        <table>
          <thead>
            <tr>
              <th>ID</th>
              <th>邮箱</th>
              <th>状态</th>
              <th>注册时间</th>
              <th>最后打卡</th>
              <th>连续天数</th>
              <th>总打卡</th>
              <th>每日提醒</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody id="userTableBody"></tbody>
        </table>
        <div class="empty" id="emptyTip" style="display:none">暂无用户数据</div>
      </div>
    </div>
  </div>
</div>

<!-- 用户详情弹窗 -->
<div class="modal-overlay" id="detailModal">
  <div class="modal">
    <div class="modal-header">
      <h3>👤 用户详情</h3>
      <button class="modal-close" onclick="closeModal('detailModal')">&times;</button>
    </div>
    <div class="modal-body" id="detailBody"></div>
  </div>
</div>

<!-- 重置密码弹窗 -->
<div class="modal-overlay" id="resetPwdModal">
  <div class="modal">
    <div class="modal-header">
      <h3>🔑 重置用户密码</h3>
      <button class="modal-close" onclick="closeModal('resetPwdModal')">&times;</button>
    </div>
    <div class="modal-body">
      <p style="margin-bottom:12px;color:#666;font-size:13px">为用户 <strong id="resetUserEmail"></strong> 设置新密码：</p>
      <div class="form-group">
        <label>新密码</label>
        <input type="text" id="newPwdInput" placeholder="输入新密码">
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn" onclick="closeModal('resetPwdModal')">取消</button>
      <button class="btn btn-primary" onclick="confirmResetPwd()">确认重置</button>
    </div>
  </div>
</div>

<!-- 删除确认弹窗 -->
<div class="modal-overlay" id="deleteModal">
  <div class="modal" style="width:380px">
    <div class="modal-header">
      <h3>⚠️ 删除用户</h3>
      <button class="modal-close" onclick="closeModal('deleteModal')">&times;</button>
    </div>
    <div class="modal-body">
      <p style="color:#c62828;font-size:14px">确定要删除用户 <strong id="deleteUserEmail"></strong> 吗？</p>
      <p style="color:#999;font-size:12px;margin-top:8px">此操作将永久删除该用户的所有数据（包括联系人和打卡记录），无法恢复。</p>
    </div>
    <div class="modal-footer">
      <button class="btn" onclick="closeModal('deleteModal')">取消</button>
      <button class="btn btn-danger" onclick="confirmDelete()">确认删除</button>
    </div>
  </div>
</div>

<!-- Toast -->
<div class="toast" id="toast"></div>

<script>
const API = '';
let token = localStorage.getItem('admin_token');
let allUsers = [];
let resetUserId = null;
let deleteUserId = null;

// 登录
async function doLogin(){
  const u = document.getElementById('loginUser').value.trim();
  const p = document.getElementById('loginPass').value;
  if(!u||!p){showError('请输入账号和密码');return}
  try{
    const res = await fetch(API+'/api/admin/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password:p})});
    const data = await res.json();
    if(res.ok){
      token = data.token;
      localStorage.setItem('admin_token', token);
      showMain();
    }else{
      showError(data.detail||'登录失败');
    }
  }catch(e){showError('网络错误')}
}

function showError(msg){
  const el=document.getElementById('loginError');
  el.textContent=msg;el.style.display='block';
  setTimeout(()=>{el.style.display='none'},3000);
}

function doLogout(){
  token=null;
  localStorage.removeItem('admin_token');
  document.getElementById('loginPage').style.display='flex';
  document.getElementById('mainPage').style.display='none';
  document.getElementById('loginUser').value='';
  document.getElementById('loginPass').value='';
}

async function showMain(){
  document.getElementById('loginPage').style.display='none';
  document.getElementById('mainPage').style.display='block';
  document.getElementById('adminName').textContent='管理员: '+ADMIN_USERNAME;
  await loadUsers();
}

// 加载用户
async function loadUsers(){
  try{
    const res = await fetch(API+'/api/admin/users');
    const data = await res.json();
    allUsers = data.users||[];
    renderStats();
    renderTable(allUsers);
  }catch(e){
    showToast('加载用户失败','error');
  }
}

function renderStats(){
  const total = allUsers.length;
  const verified = allUsers.filter(u=>u.is_verified).length;
  const today = new Date().toISOString().slice(0,10);
  const checkedToday = allUsers.filter(u=>u.last_check_in && u.last_check_in.startsWith(today)).length;
  const reminderOn = allUsers.filter(u=>u.daily_reminder).length;
  
  document.getElementById('statsArea').innerHTML = `
    <div class="stat-card"><div class="num">${total}</div><div class="label">总用户数</div></div>
    <div class="stat-card"><div class="num">${verified}</div><div class="label">已验证用户</div></div>
    <div class="stat-card"><div class="num">${checkedToday}</div><div class="label">今日已打卡</div></div>
    <div class="stat-card"><div class="num">${reminderOn}</div><div class="label">开启提醒</div></div>
  `;
}

function renderTable(users){
  const tbody = document.getElementById('userTableBody');
  const empty = document.getElementById('emptyTip');
  if(users.length===0){
    tbody.innerHTML='';
    empty.style.display='block';
    return;
  }
  empty.style.display='none';
  tbody.innerHTML = users.map(u=>`
    <tr>
      <td>${u.id}</td>
      <td style="font-weight:500">${esc(u.email)}</td>
      <td><span class="badge ${u.is_verified?'badge-green':'badge-gray'}">${u.is_verified?'已验证':'未验证'}</span></td>
      <td style="font-size:12px;color:#999">${fmtDate(u.created_at)}</td>
      <td style="font-size:12px">${u.last_check_in?fmtDate(u.last_check_in):'<span style="color:#ccc">-</span>'}</td>
      <td>${u.consecutive_days||0}</td>
      <td><span class="badge badge-blue">${u.check_in_count||0}</span></td>
      <td>${u.daily_reminder?'✅':'❌'}</td>
      <td class="actions">
        <button class="btn btn-sm btn-primary" onclick="viewUser(${u.id})">详情</button>
        <button class="btn btn-sm btn-warning" onclick="openResetPwd(${u.id},'${esc(u.email)}')">重置密码</button>
        <button class="btn btn-sm btn-danger" onclick="openDelete(${u.id},'${esc(u.email)}')">删除</button>
      </td>
    </tr>
  `).join('');
}

function filterUsers(){
  const q = document.getElementById('searchInput').value.trim().toLowerCase();
  if(!q){renderTable(allUsers);return}
  renderTable(allUsers.filter(u=>u.email.toLowerCase().includes(q)));
}

// 查看用户详情
async function viewUser(id){
  try{
    const res = await fetch(API+'/api/admin/user/'+id);
    if(!res.ok){showToast('获取失败','error');return}
    const u = await res.json();
    const grace = u.grace_period_hours+'小时'+(u.grace_period_minutes>0?u.grace_period_minutes+'分钟':'');
    
    let contactsHtml = u.contacts&&u.contacts.length?
      u.contacts.map(c=>`<tr><td>${esc(c.name)}</td><td>${esc(c.email)}</td><td>${esc(c.phone||'-')}</td></tr>`).join('')
      :'<tr><td colspan="3" style="text-align:center;color:#999;padding:16px">暂无联系人</td></tr>';
    
    let checkinsHtml = u.recent_checkins&&u.recent_checkins.length?
      u.recent_checkins.map(c=>`<tr><td>${fmtDate(c.timestamp)}</td><td>${esc(c.note||'-')}</td></tr>`).join('')
      :'<tr><td colspan="2" style="text-align:center;color:#999;padding:16px">暂无打卡记录</td></tr>';
    
    document.getElementById('detailBody').innerHTML = `
      <div class="detail-grid">
        <div class="detail-item"><div class="dl">用户ID</div><div class="dv">${u.id}</div></div>
        <div class="detail-item"><div class="dl">邮箱</div><div class="dv">${esc(u.email)}</div></div>
        <div class="detail-item"><div class="dl">状态</div><div class="dv">${u.is_verified?'✅ 已验证':'❌ 未验证'}</div></div>
        <div class="detail-item"><div class="dl">注册时间</div><div class="dv">${fmtDate(u.created_at)}</div></div>
        <div class="detail-item"><div class="dl">最后打卡</div><div class="dv">${u.last_check_in?fmtDate(u.last_check_in):'-'}</div></div>
        <div class="detail-item"><div class="dl">连续打卡</div><div class="dv">${u.consecutive_days||0} 天</div></div>
        <div class="detail-item"><div class="dl">总打卡次数</div><div class="dv">${u.check_in_count||0} 次</div></div>
        <div class="detail-item"><div class="dl">每日提醒</div><div class="dv">${u.daily_reminder?'✅ 已开启':'❌ 已关闭'}</div></div>
        <div class="detail-item"><div class="dl">宽限期</div><div class="dv">${grace}</div></div>
      </div>
      <div class="form-group" style="margin-top:16px">
        <label>预设消息</label>
        <textarea readonly style="background:#f9fafb">${esc(u.preset_message||'')}</textarea>
      </div>
      <div class="sub-section">
        <h4>📇 联系人 (${(u.contacts||[]).length})</h4>
        <table><thead><tr><th>姓名</th><th>邮箱</th><th>电话</th></tr></thead><tbody>${contactsHtml}</tbody></table>
      </div>
      <div class="sub-section">
        <h4>✅ 最近打卡记录 (${(u.recent_checkins||[]).length})</h4>
        <table><thead><tr><th>时间</th><th>备注</th></tr></thead><tbody>${checkinsHtml}</tbody></table>
      </div>
    `;
    openModal('detailModal');
  }catch(e){showToast('获取详情失败','error')}
}

// 重置密码
function openResetPwd(id, email){
  resetUserId = id;
  document.getElementById('resetUserEmail').textContent = email;
  document.getElementById('newPwdInput').value = '';
  openModal('resetPwdModal');
}

async function confirmResetPwd(){
  const pwd = document.getElementById('newPwdInput').value.trim();
  if(!pwd){showToast('请输入新密码','error');return}
  try{
    const res = await fetch(API+'/api/admin/reset-password',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({user_id:resetUserId,new_password:pwd})});
    const data = await res.json();
    if(res.ok){showToast('密码已重置','success');closeModal('resetPwdModal');loadUsers()}
    else showToast(data.detail||'重置失败','error');
  }catch(e){showToast('网络错误','error')}
}

// 删除用户
function openDelete(id, email){
  deleteUserId = id;
  document.getElementById('deleteUserEmail').textContent = email;
  openModal('deleteModal');
}

async function confirmDelete(){
  try{
    const res = await fetch(API+'/api/admin/users/'+deleteUserId,{method:'DELETE'});
    const data = await res.json();
    if(res.ok){showToast('用户已删除','success');closeModal('deleteModal');loadUsers()}
    else showToast(data.detail||'删除失败','error');
  }catch(e){showToast('网络错误','error')}
}

// Modal
function openModal(id){document.getElementById(id).classList.add('active')}
function closeModal(id){document.getElementById(id).classList.remove('active')}

// Toast
function showToast(msg, type){
  const t=document.getElementById('toast');
  t.textContent=msg;
  t.className='toast toast-'+type+' show';
  setTimeout(()=>{t.classList.remove('show')},2500);
}

// 工具函数
function esc(s){if(!s)return'';return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
function fmtDate(s){if(!s)return'-';return s.replace('T',' ').replace(/\.\d+$/,'')}

// 管理员用户名常量（前端显示用）
const ADMIN_USERNAME = 'root';

// 自动登录
if(token){showMain()}
// 回车登录
document.getElementById('loginPass').addEventListener('keydown',function(e){if(e.key==='Enter')doLogin()});
</script>
</body>
</html>"""

# ========== 根路由 ==========
@app.get("/")
def root():
    return {"message": "SafeCheckIn Cloud API v1.0", "status": "running"}

# ========== 启动服务器 ==========
if __name__ == "__main__":
    # 启动主 API 服务器 (8000) 和管理后台 (9090)
    import threading
    admin_app = FastAPI(title="SafeCheckIn Admin", version="1.0")
    
    # 复制管理后台路由到 admin_app
    @admin_app.get("/admin", response_class=HTMLResponse)
    def admin_page_9090():
        return ADMIN_HTML
    
    @admin_app.post("/api/admin/login")
    def admin_login_9090(data: AdminLoginModel):
        if data.username != ADMIN_USERNAME or data.password != ADMIN_PASSWORD:
            raise HTTPException(status_code=401, detail="管理员账号或密码错误")
        return {"message": "登录成功", "token": secrets.token_hex(32)}
    
    @admin_app.get("/api/admin/users")
    def admin_users_9090(conn=Depends(get_db)):
        c = conn.cursor()
        c.execute('SELECT id, email, is_verified, created_at, last_check_in, check_in_count, consecutive_days, grace_period_hours, grace_period_minutes, daily_reminder, preset_message FROM users')
        return {"users": [dict(r) for r in c.fetchall()]}
    
    @admin_app.get("/api/admin/user/{user_id}")
    def admin_user_detail_9090(user_id: int, conn=Depends(get_db)):
        c = conn.cursor()
        c.execute('SELECT * FROM users WHERE id=?', (user_id,))
        row = c.fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="用户不存在")
        c.execute('SELECT * FROM contacts WHERE user_id=?', (user_id,))
        contacts = [dict(r) for r in c.fetchall()]
        c.execute('SELECT * FROM check_ins WHERE user_id=? ORDER BY timestamp DESC LIMIT 20', (user_id,))
        checkins = [dict(r) for r in c.fetchall()]
        user = dict(row)
        user['contacts'] = contacts
        user['recent_checkins'] = checkins
        return user
    
    @admin_app.post("/api/admin/reset-password")
    def admin_reset_pwd_9090(data: AdminPasswordReset, conn=Depends(get_db)):
        c = conn.cursor()
        c.execute('SELECT id FROM users WHERE id=?', (data.user_id,))
        if not c.fetchone():
            raise HTTPException(status_code=404, detail="用户不存在")
        c.execute('UPDATE users SET password_hash=? WHERE id=?', (hash_password(data.new_password), data.user_id))
        conn.commit()
        return {"message": "用户密码已重置"}
    
    @admin_app.delete("/api/admin/users/{user_id}")
    def admin_delete_9090(user_id: int, conn=Depends(get_db)):
        c = conn.cursor()
        c.execute('SELECT id FROM users WHERE id=?', (user_id,))
        if not c.fetchone():
            raise HTTPException(status_code=404, detail="用户不存在")
        c.execute('DELETE FROM contacts WHERE user_id=?', (user_id,))
        c.execute('DELETE FROM check_ins WHERE user_id=?', (user_id,))
        c.execute('DELETE FROM users WHERE id=?', (user_id,))
        conn.commit()
        return {"message": "用户已删除"}
    
    def run_admin():
        uvicorn.run(admin_app, host="0.0.0.0", port=9090, log_level="warning")
    
    admin_thread = threading.Thread(target=run_admin, daemon=True)
    admin_thread.start()
    print("[Admin] 管理后台已启动: http://0.0.0.0:9090/admin")
    
    uvicorn.run(app, host="0.0.0.0", port=8000)
