"""Hera Planner - FastAPI Backend"""
import json
import os
import hashlib
import time
import shutil
import threading
from datetime import datetime
from pathlib import Path
from contextlib import contextmanager

from fastapi import FastAPI, HTTPException, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from pydantic import BaseModel
from typing import Optional

from .database import get_db, init_db, DB_PATH

app = FastAPI(title="Hera's Planner")

DATA_DIR = os.environ.get("DATA_DIR", "/data")
BACKUP_DIR = os.path.join(DATA_DIR, "backups")
FRONTEND_DIR = os.environ.get("FRONTEND_DIR", "/app/frontend")

DEFAULT_HASH = "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"


# ============================================================
#  Helpers
# ============================================================

def _ensure_dirs():
    Path(DATA_DIR).mkdir(parents=True, exist_ok=True)
    Path(BACKUP_DIR).mkdir(parents=True, exist_ok=True)


def _get_config(key, default=None):
    conn = get_db()
    row = conn.execute("SELECT value FROM config WHERE key = ?", (key,)).fetchone()
    conn.close()
    return row["value"] if row else default


def _set_config(key, value):
    conn = get_db()
    conn.execute(
        "INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?",
        (key, value, value),
    )
    conn.commit()
    conn.close()


def _do_backup():
    """Backup the SQLite database."""
    if not os.path.exists(DB_PATH):
        return
    _ensure_dirs()
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    dest = os.path.join(BACKUP_DIR, f"planner_{ts}.db")
    # Use SQLite backup API for consistency
    import sqlite3
    src_conn = sqlite3.connect(DB_PATH)
    dst_conn = sqlite3.connect(dest)
    src_conn.backup(dst_conn)
    dst_conn.close()
    src_conn.close()
    # Keep only last 30 backups
    backups = sorted(Path(BACKUP_DIR).glob("planner_*.db"))
    for old in backups[:-30]:
        old.unlink()


# WAL checkpoint thread
def _wal_checkpoint_loop():
    while True:
        time.sleep(300)
        try:
            conn = get_db()
            conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
            conn.close()
        except Exception:
            pass


# ============================================================
#  Startup
# ============================================================

@app.on_event("startup")
def startup():
    _ensure_dirs()
    init_db()
    # Start WAL checkpoint thread
    t = threading.Thread(target=_wal_checkpoint_loop, daemon=True)
    t.start()
    # Mount frontend static files
    dist_assets = os.path.join(FRONTEND_DIR, "assets")
    if os.path.isdir(dist_assets):
        app.mount("/assets", StaticFiles(directory=dist_assets), name="assets")


# ============================================================
#  Auth
# ============================================================

class LoginRequest(BaseModel):
    hash: str

class ChangePasswordRequest(BaseModel):
    oldHash: str
    newHash: str


@app.post("/api/login")
def login(req: LoginRequest):
    stored = _get_config("password_hash", DEFAULT_HASH)
    if req.hash != stored:
        raise HTTPException(401, "密码不正确")
    return {"ok": True}


@app.post("/api/change-password")
def change_password(req: ChangePasswordRequest):
    stored = _get_config("password_hash", DEFAULT_HASH)
    if req.oldHash != stored:
        raise HTTPException(401, "当前密码不正确")
    _set_config("password_hash", req.newHash)
    return {"ok": True}


# ============================================================
#  Notes (随手记)
# ============================================================

@app.get("/api/notes")
def list_notes():
    conn = get_db()
    rows = conn.execute("SELECT * FROM notes ORDER BY created_at DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class NoteIn(BaseModel):
    id: str
    text: str
    tag: str = "灵感"

@app.post("/api/notes")
def create_note(note: NoteIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO notes (id, text, tag) VALUES (?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET text=?, tag=?, updated_at=datetime('now')",
        (note.id, note.text, note.tag, note.text, note.tag),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/notes/{note_id}")
def delete_note(note_id: str):
    conn = get_db()
    conn.execute("DELETE FROM notes WHERE id = ?", (note_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Todos (待办四象限)
# ============================================================

@app.get("/api/todos")
def list_todos():
    conn = get_db()
    rows = conn.execute("SELECT * FROM todos ORDER BY created_at DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class TodoIn(BaseModel):
    id: str
    text: str
    quadrant: str = "q1"
    done: int = 0

@app.post("/api/todos")
def upsert_todo(todo: TodoIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO todos (id, text, quadrant, done) VALUES (?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET text=?, quadrant=?, done=?, updated_at=datetime('now')",
        (todo.id, todo.text, todo.quadrant, todo.done, todo.text, todo.quadrant, todo.done),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/todos/{todo_id}")
def delete_todo(todo_id: str):
    conn = get_db()
    conn.execute("DELETE FROM todos WHERE id = ?", (todo_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Inbox (收集箱)
# ============================================================

@app.get("/api/inbox")
def list_inbox():
    conn = get_db()
    rows = conn.execute("SELECT * FROM inbox ORDER BY created_at DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class InboxIn(BaseModel):
    id: str
    text: str

@app.post("/api/inbox")
def add_inbox(item: InboxIn):
    conn = get_db()
    conn.execute(
        "INSERT OR REPLACE INTO inbox (id, text) VALUES (?, ?)",
        (item.id, item.text),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/inbox/{item_id}")
def delete_inbox(item_id: str):
    conn = get_db()
    conn.execute("DELETE FROM inbox WHERE id = ?", (item_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/inbox")
def clear_inbox():
    conn = get_db()
    conn.execute("DELETE FROM inbox")
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Reminders (提醒)
# ============================================================

@app.get("/api/reminders")
def list_reminders():
    conn = get_db()
    rows = conn.execute("SELECT * FROM reminders ORDER BY time").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class ReminderIn(BaseModel):
    id: str
    text: str
    time: Optional[str] = None
    repeat: str = "none"
    enabled: int = 1

@app.post("/api/reminders")
def upsert_reminder(r: ReminderIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO reminders (id, text, time, repeat, enabled) VALUES (?, ?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET text=?, time=?, repeat=?, enabled=?",
        (r.id, r.text, r.time, r.repeat, r.enabled, r.text, r.time, r.repeat, r.enabled),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/reminders/{reminder_id}")
def delete_reminder(reminder_id: str):
    conn = get_db()
    conn.execute("DELETE FROM reminders WHERE id = ?", (reminder_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Goals (目标)
# ============================================================

@app.get("/api/goals")
def list_goals():
    conn = get_db()
    rows = conn.execute("SELECT * FROM goals ORDER BY created_at DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class GoalIn(BaseModel):
    id: str
    name: str
    month: Optional[str] = None
    checks: str = "{}"

@app.post("/api/goals")
def upsert_goal(g: GoalIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO goals (id, name, month, checks) VALUES (?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET name=?, month=?, checks=?",
        (g.id, g.name, g.month, g.checks, g.name, g.month, g.checks),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/goals/{goal_id}")
def delete_goal(goal_id: str):
    conn = get_db()
    conn.execute("DELETE FROM goals WHERE id = ?", (goal_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Checklists (清单)
# ============================================================

@app.get("/api/checklists")
def list_checklists():
    conn = get_db()
    rows = conn.execute("SELECT * FROM checklists ORDER BY created_at DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class ChecklistIn(BaseModel):
    id: str
    title: str
    items: str = "[]"
    archived: int = 0

@app.post("/api/checklists")
def upsert_checklist(cl: ChecklistIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO checklists (id, title, items, archived) VALUES (?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET title=?, items=?, archived=?",
        (cl.id, cl.title, cl.items, cl.archived, cl.title, cl.items, cl.archived),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/checklists/{cl_id}")
def delete_checklist(cl_id: str):
    conn = get_db()
    conn.execute("DELETE FROM checklists WHERE id = ?", (cl_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Health / Music Check-in (打卡)
# ============================================================

@app.get("/api/health-items")
def list_health_items(type: str = "health"):
    conn = get_db()
    rows = conn.execute("SELECT * FROM health_items WHERE type = ?", (type,)).fetchall()
    conn.close()
    return [dict(r) for r in rows]


class HealthItemIn(BaseModel):
    id: str
    name: str
    type: str = "health"

@app.post("/api/health-items")
def upsert_health_item(item: HealthItemIn):
    conn = get_db()
    conn.execute(
        "INSERT OR REPLACE INTO health_items (id, name, type) VALUES (?, ?, ?)",
        (item.id, item.name, item.type),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/health-items/{item_id}")
def delete_health_item(item_id: str):
    conn = get_db()
    conn.execute("DELETE FROM health_items WHERE id = ?", (item_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


@app.get("/api/health-plans")
def get_health_plans(type: str = "health"):
    conn = get_db()
    rows = conn.execute("SELECT * FROM health_plans WHERE type = ?", (type,)).fetchall()
    conn.close()
    return [dict(r) for r in rows]


class HealthPlanIn(BaseModel):
    month: str
    type: str = "health"
    item_ids: str = "[]"

@app.post("/api/health-plans")
def upsert_health_plan(plan: HealthPlanIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO health_plans (month, type, item_ids) VALUES (?, ?, ?) "
        "ON CONFLICT(month, type) DO UPDATE SET item_ids=?",
        (plan.month, plan.type, plan.item_ids, plan.item_ids),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.get("/api/health-checks")
def list_health_checks(type: str = "health", month: Optional[str] = None):
    conn = get_db()
    if month:
        rows = conn.execute(
            "SELECT * FROM health_checks WHERE type = ? AND date LIKE ?",
            (type, f"{month}%"),
        ).fetchall()
    else:
        rows = conn.execute("SELECT * FROM health_checks WHERE type = ?", (type,)).fetchall()
    conn.close()
    return [dict(r) for r in rows]


class HealthCheckIn(BaseModel):
    date: str
    type: str = "health"
    item_id: str
    checked: int = 1

@app.post("/api/health-checks")
def upsert_health_check(check: HealthCheckIn):
    conn = get_db()
    if check.checked:
        conn.execute(
            "INSERT OR REPLACE INTO health_checks (date, type, item_id, checked) VALUES (?, ?, ?, ?)",
            (check.date, check.type, check.item_id, check.checked),
        )
    else:
        conn.execute(
            "DELETE FROM health_checks WHERE date = ? AND type = ? AND item_id = ?",
            (check.date, check.type, check.item_id),
        )
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Sleep Records (睡眠)
# ============================================================

@app.get("/api/sleep")
def list_sleep():
    conn = get_db()
    rows = conn.execute("SELECT * FROM sleep_records ORDER BY date DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class SleepIn(BaseModel):
    date: str
    time: str
    minutes: Optional[float] = None

@app.post("/api/sleep")
def upsert_sleep(record: SleepIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO sleep_records (date, time, minutes) VALUES (?, ?, ?) "
        "ON CONFLICT(date) DO UPDATE SET time=?, minutes=?",
        (record.date, record.time, record.minutes, record.time, record.minutes),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/sleep/{date}")
def delete_sleep(date: str):
    conn = get_db()
    conn.execute("DELETE FROM sleep_records WHERE date = ?", (date,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Gym Records (健身)
# ============================================================

@app.get("/api/gym")
def list_gym():
    conn = get_db()
    rows = conn.execute("SELECT * FROM gym_records ORDER BY date DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class GymIn(BaseModel):
    id: str
    date: str
    type: str = ""
    duration: str = ""
    note: str = ""

@app.post("/api/gym")
def upsert_gym(record: GymIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO gym_records (id, date, type, duration, note) VALUES (?, ?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET date=?, type=?, duration=?, note=?",
        (record.id, record.date, record.type, record.duration, record.note,
         record.date, record.type, record.duration, record.note),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/gym/{record_id}")
def delete_gym(record_id: str):
    conn = get_db()
    conn.execute("DELETE FROM gym_records WHERE id = ?", (record_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Period Records (经期)
# ============================================================

@app.get("/api/period")
def list_period():
    conn = get_db()
    rows = conn.execute("SELECT * FROM period_records ORDER BY start_date DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class PeriodIn(BaseModel):
    id: str
    start_date: str
    end_date: Optional[str] = None
    note: str = ""

@app.post("/api/period")
def upsert_period(record: PeriodIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO period_records (id, start_date, end_date, note) VALUES (?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET start_date=?, end_date=?, note=?",
        (record.id, record.start_date, record.end_date, record.note,
         record.start_date, record.end_date, record.note),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/period/{record_id}")
def delete_period(record_id: str):
    conn = get_db()
    conn.execute("DELETE FROM period_records WHERE id = ?", (record_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Docs (文档)
# ============================================================

@app.get("/api/docs")
def list_docs():
    conn = get_db()
    docs = conn.execute("SELECT * FROM docs ORDER BY created_at").fetchall()
    result = []
    for d in docs:
        doc = dict(d)
        entries = conn.execute(
            "SELECT * FROM doc_entries WHERE doc_id = ? ORDER BY created_at DESC",
            (d["id"],),
        ).fetchall()
        doc["entries"] = [dict(e) for e in entries]
        result.append(doc)
    conn.close()
    return result


class DocIn(BaseModel):
    id: str
    name: str
    icon: str = "📄"
    keywords: str = ""
    extract_rule: str = "none"

@app.post("/api/docs")
def upsert_doc(doc: DocIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO docs (id, name, icon, keywords, extract_rule) VALUES (?, ?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET name=?, icon=?, keywords=?, extract_rule=?",
        (doc.id, doc.name, doc.icon, doc.keywords, doc.extract_rule,
         doc.name, doc.icon, doc.keywords, doc.extract_rule),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/docs/{doc_id}")
def delete_doc(doc_id: str):
    conn = get_db()
    conn.execute("DELETE FROM docs WHERE id = ?", (doc_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


class DocEntryIn(BaseModel):
    id: str
    doc_id: str
    text: str
    note_id: Optional[str] = None

@app.post("/api/doc-entries")
def add_doc_entry(entry: DocEntryIn):
    conn = get_db()
    conn.execute(
        "INSERT OR REPLACE INTO doc_entries (id, doc_id, text, note_id) VALUES (?, ?, ?, ?)",
        (entry.id, entry.doc_id, entry.text, entry.note_id),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/doc-entries/{entry_id}")
def delete_doc_entry(entry_id: str):
    conn = get_db()
    conn.execute("DELETE FROM doc_entries WHERE id = ?", (entry_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Schedule (日程)
# ============================================================

@app.get("/api/schedule-modules")
def list_schedule_modules():
    conn = get_db()
    rows = conn.execute("SELECT * FROM schedule_modules ORDER BY sort_order").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class ScheduleModuleIn(BaseModel):
    id: str
    name: str
    emoji: str = "📌"
    color: str = "#667eea"
    sort_order: int = 0

@app.post("/api/schedule-modules")
def upsert_schedule_module(m: ScheduleModuleIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO schedule_modules (id, name, emoji, color, sort_order) VALUES (?, ?, ?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET name=?, emoji=?, color=?, sort_order=?",
        (m.id, m.name, m.emoji, m.color, m.sort_order, m.name, m.emoji, m.color, m.sort_order),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/schedule-modules/{module_id}")
def delete_schedule_module(module_id: str):
    conn = get_db()
    conn.execute("DELETE FROM schedule_modules WHERE id = ?", (module_id,))
    conn.execute("DELETE FROM schedule_slots WHERE module_id = ?", (module_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


@app.get("/api/schedule-slots")
def list_schedule_slots(date: Optional[str] = None):
    conn = get_db()
    if date:
        rows = conn.execute("SELECT * FROM schedule_slots WHERE date = ?", (date,)).fetchall()
    else:
        rows = conn.execute("SELECT * FROM schedule_slots").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class ScheduleSlotIn(BaseModel):
    date: str
    time_slot: str
    module_id: str

@app.post("/api/schedule-slots")
def add_schedule_slot(slot: ScheduleSlotIn):
    conn = get_db()
    conn.execute(
        "INSERT OR REPLACE INTO schedule_slots (date, time_slot, module_id) VALUES (?, ?, ?)",
        (slot.date, slot.time_slot, slot.module_id),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/schedule-slots")
def delete_schedule_slots(date: str, time_slot: Optional[str] = None, module_id: Optional[str] = None):
    conn = get_db()
    if time_slot and module_id:
        conn.execute(
            "DELETE FROM schedule_slots WHERE date = ? AND time_slot = ? AND module_id = ?",
            (date, time_slot, module_id),
        )
    elif time_slot:
        conn.execute("DELETE FROM schedule_slots WHERE date = ? AND time_slot = ?", (date, time_slot))
    else:
        conn.execute("DELETE FROM schedule_slots WHERE date = ?", (date,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Weekly Template (每周模板)
# ============================================================

@app.get("/api/weekly-template")
def get_weekly_template():
    conn = get_db()
    rows = conn.execute("SELECT * FROM weekly_template ORDER BY day").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class WeeklyTemplateIn(BaseModel):
    day: int
    data: str = "[]"

@app.post("/api/weekly-template")
def upsert_weekly_template(t: WeeklyTemplateIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO weekly_template (day, data) VALUES (?, ?) "
        "ON CONFLICT(day) DO UPDATE SET data=?",
        (t.day, t.data, t.data),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Reviews (周回顾)
# ============================================================

@app.get("/api/reviews")
def list_reviews():
    conn = get_db()
    rows = conn.execute("SELECT * FROM reviews ORDER BY week DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class ReviewIn(BaseModel):
    week: str
    data: str = "{}"

@app.post("/api/reviews")
def upsert_review(r: ReviewIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO reviews (week, data) VALUES (?, ?) "
        "ON CONFLICT(week) DO UPDATE SET data=?",
        (r.week, r.data, r.data),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Bugs
# ============================================================

@app.get("/api/bugs")
def list_bugs():
    conn = get_db()
    rows = conn.execute("SELECT * FROM bugs ORDER BY created_at DESC").fetchall()
    conn.close()
    return [dict(r) for r in rows]


class BugIn(BaseModel):
    id: str
    text: str
    status: str = "open"

@app.post("/api/bugs")
def upsert_bug(bug: BugIn):
    conn = get_db()
    conn.execute(
        "INSERT INTO bugs (id, text, status) VALUES (?, ?, ?) "
        "ON CONFLICT(id) DO UPDATE SET text=?, status=?",
        (bug.id, bug.text, bug.status, bug.text, bug.status),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


@app.delete("/api/bugs/{bug_id}")
def delete_bug(bug_id: str):
    conn = get_db()
    conn.execute("DELETE FROM bugs WHERE id = ?", (bug_id,))
    conn.commit()
    conn.close()
    return {"ok": True}


# ============================================================
#  Sleep Buddy (独立的社交睡眠打卡)
# ============================================================

class BuddyRegister(BaseModel):
    username: str
    hash: str

@app.post("/api/buddy-register")
def buddy_register(req: BuddyRegister):
    username = req.username.strip()
    if not username or not req.hash:
        raise HTTPException(400, "请输入用户名和密码")
    conn = get_db()
    existing = conn.execute("SELECT username FROM buddy_users WHERE username = ?", (username,)).fetchone()
    if existing:
        conn.close()
        raise HTTPException(400, "用户名已存在")
    conn.execute(
        "INSERT INTO buddy_users (username, password_hash) VALUES (?, ?)",
        (username, req.hash),
    )
    conn.commit()
    conn.close()
    return {"ok": True}


class BuddyLogin(BaseModel):
    username: str
    hash: str

@app.post("/api/buddy-login")
def buddy_login(req: BuddyLogin):
    username = req.username.strip()
    conn = get_db()
    user = conn.execute(
        "SELECT * FROM buddy_users WHERE username = ? AND password_hash = ?",
        (username, req.hash),
    ).fetchone()
    conn.close()
    if not user:
        raise HTTPException(401, "用户名或密码不正确")
    return {"ok": True, "username": username}


class BuddyDeleteUser(BaseModel):
    adminHash: str
    username: str

@app.post("/api/buddy-delete-user")
def buddy_delete_user(req: BuddyDeleteUser):
    stored = _get_config("password_hash", DEFAULT_HASH)
    if req.adminHash != stored:
        raise HTTPException(401, "需要管理员密码")
    conn = get_db()
    conn.execute("DELETE FROM buddy_users WHERE username = ?", (req.username,))
    conn.execute("DELETE FROM buddy_records WHERE username = ?", (req.username,))
    conn.commit()
    conn.close()
    return {"ok": True}


@app.get("/api/sleep-buddy")
def get_buddy_data():
    conn = get_db()
    users_rows = conn.execute("SELECT * FROM buddy_users").fetchall()
    users = {}
    for u in users_rows:
        records = conn.execute(
            "SELECT date, time FROM buddy_records WHERE username = ? ORDER BY date DESC LIMIT 60",
            (u["username"],),
        ).fetchall()
        users[u["username"]] = [dict(r) for r in records]

    targets = {}
    for u in users_rows:
        targets[u["username"]] = u["target_time"]

    notifs = conn.execute(
        "SELECT * FROM buddy_notifications ORDER BY created_at DESC LIMIT 20"
    ).fetchall()
    conn.close()

    return {
        "users": users,
        "targets": targets,
        "notifications": [dict(n) for n in notifs],
    }


class BuddyAction(BaseModel):
    user: str
    action: str
    record: Optional[dict] = None
    date: Optional[str] = None
    target: Optional[str] = None

@app.post("/api/sleep-buddy")
def buddy_action(req: BuddyAction):
    conn = get_db()

    if req.action == "record":
        record = req.record or {}
        conn.execute(
            "INSERT INTO buddy_records (username, date, time) VALUES (?, ?, ?) "
            "ON CONFLICT(username, date) DO UPDATE SET time=?",
            (req.user, record.get("date", ""), record.get("time", ""), record.get("time", "")),
        )
        conn.commit()
        conn.close()
        return {"ok": True}

    elif req.action == "delete-record":
        conn.execute(
            "DELETE FROM buddy_records WHERE username = ? AND date = ?",
            (req.user, req.date),
        )
        conn.commit()
        conn.close()
        return {"ok": True}

    elif req.action == "sleep-now":
        now = datetime.now()
        conn.execute(
            "INSERT INTO buddy_notifications (from_user, message, time, date, created_at) "
            "VALUES (?, ?, ?, ?, ?)",
            (
                req.user,
                f"{req.user} 去睡觉啦，你也早点休息！",
                now.strftime("%H:%M"),
                now.strftime("%Y-%m-%d"),
                time.time(),
            ),
        )
        # Keep only last 20 notifications
        conn.execute("""
            DELETE FROM buddy_notifications WHERE id NOT IN (
                SELECT id FROM buddy_notifications ORDER BY created_at DESC LIMIT 20
            )
        """)
        conn.commit()
        conn.close()
        return {"ok": True}

    elif req.action == "get-notifications":
        cutoff = time.time() - 86400
        rows = conn.execute(
            "SELECT * FROM buddy_notifications WHERE from_user != ? AND created_at > ? ORDER BY created_at DESC",
            (req.user, cutoff),
        ).fetchall()
        conn.close()
        return {"notifications": [dict(r) for r in rows]}

    elif req.action == "set-target":
        conn.execute(
            "UPDATE buddy_users SET target_time = ? WHERE username = ?",
            (req.target or "22:00", req.user),
        )
        conn.commit()
        conn.close()
        return {"ok": True}

    conn.close()
    return {"ok": True}


# ============================================================
#  Backups
# ============================================================

@app.get("/api/backups")
def list_backups():
    _ensure_dirs()
    backups = sorted(Path(BACKUP_DIR).glob("planner_*.db"), reverse=True)
    items = []
    for b in backups[:20]:
        items.append({
            "name": b.name,
            "size": b.stat().st_size,
            "time": b.stem.split("_", 1)[1],
        })
    return items


@app.post("/api/backup")
def trigger_backup():
    _do_backup()
    return {"ok": True}


# ============================================================
#  SPA Fallback
# ============================================================

@app.get("/{path:path}")
async def spa_fallback(path: str):
    # Try static file first
    file_path = os.path.join(FRONTEND_DIR, path)
    if path and os.path.isfile(file_path):
        return FileResponse(file_path)
    # SPA fallback
    index = os.path.join(FRONTEND_DIR, "index.html")
    if os.path.isfile(index):
        return FileResponse(index, headers={
            "Cache-Control": "no-cache, no-store, must-revalidate",
        })
    return JSONResponse({"error": "Not found"}, status_code=404)
