"""Backup / Restore — export and re-import chat settings.

Owner-only. The bot database is a single SQLite file; we send it to the owner
on `/backup` and accept a replacement via `/restore` (replied-to a document).

New in v3.0: auto-backup — scheduled periodic backups saved to disk.
"""
from __future__ import annotations

import io
import json as json_mod
import logging
import os
import shutil
import time
from pathlib import Path
from typing import Optional

from telegram import Update
from telegram.constants import ParseMode
from telegram.error import BadRequest
from telegram.ext import ContextTypes

from bot import config, db
from bot.decorators import owner_only, sudo_only
from bot.persian_router import register_mixed

logger = logging.getLogger(__name__)

BACKUP_DIR = Path("data/backups")


async def _do_auto_backup() -> Optional[str]:
    """Create a timestamped backup file. Returns filename or None."""
    db_path = Path(config.DB_PATH)
    if not db_path.exists():
        return None
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    ts = int(time.time())
    dest = BACKUP_DIR / f"bobi-auto-{ts}.db"
    try:
        # WAL-safe: checkpoint before copy
        conn = await db.get_conn()
        await conn.execute("PRAGMA wal_checkpoint(FULL)")
        shutil.copy2(db_path, dest)
        size = dest.stat().st_size
        await db._exec(
            "INSERT INTO backups (chat_id, filename, size_bytes) VALUES (NULL, ?, ?)",
            (dest.name, size),
        )
        # Keep only last 10 backups
        all_backups = sorted(BACKUP_DIR.glob("bobi-auto-*.db"), key=lambda p: p.stat().st_mtime)
        for old in all_backups[:-10]:
            old.unlink(missing_ok=True)
        return dest.name
    except Exception as e:
        logger.error("auto-backup failed: %s", e)
        return None


@owner_only
async def backup_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Send a copy of the SQLite database to the owner."""
    db_path = Path(config.DB_PATH)
    if not db_path.exists():
        await update.message.reply_text("❌ فایل دیتابیس یافت نشد.")
        return
    try:
        # Build a snapshot to avoid sending a half-written DB
        snapshot = db_path.with_suffix(f".snapshot.{int(time.time())}.db")
        shutil.copy2(db_path, snapshot)
        size_kb = snapshot.stat().st_size / 1024
        with open(snapshot, "rb") as f:
            await update.message.reply_document(
                document=f,
                filename=f"bobi-backup-{int(time.time())}.db",
                caption=f"📦 بکاپ دیتابیس bobi\nحجم: {size_kb:.1f} KB",
            )
        snapshot.unlink(missing_ok=True)
    except Exception as e:
        logger.exception("backup error: %s", e)
        await update.message.reply_text(f"⚠️ خطا در ساخت بکاپ: {e}")


@owner_only
async def exportchat_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Export this chat's settings/welcome/rules/notes as a JSON file."""
    from bot import db
    import json
    chat = update.effective_chat
    chat_id = chat.id

    data: dict = {"chat_id": chat_id, "title": chat.title, "exported_at": int(time.time())}

    try:
        conn = await db.get_conn()
        cur = await conn.execute("SELECT key, value FROM chat_settings WHERE chat_id=?", (chat_id,))
        data["settings"] = {r["key"]: r["value"] for r in await cur.fetchall()}

        cur = await conn.execute("SELECT name, content, file_id, file_type, buttons FROM notes WHERE chat_id=?", (chat_id,))
        data["notes"] = [dict(r) for r in await cur.fetchall()]

        cur = await conn.execute("SELECT keyword, response, file_id, file_type FROM filters WHERE chat_id=?", (chat_id,))
        data["filters"] = [dict(r) for r in await cur.fetchall()]

        cur = await conn.execute("SELECT lock_type FROM locks WHERE chat_id=?", (chat_id,))
        data["locks"] = [r["lock_type"] for r in await cur.fetchall()]

        cur = await conn.execute("SELECT key, value FROM chat_settings WHERE chat_id=? AND key='welcome_text'", (chat_id,))
        row = await cur.fetchone()
        data["welcome"] = row["value"] if row else None
    except Exception as e:
        await update.message.reply_text(f"⚠️ خطا: {e}")
        return

    buf = io.BytesIO(json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8"))
    buf.name = f"chat-{chat_id}-export.json"
    await update.message.reply_document(
        document=buf,
        caption=f"📤 خروجی تنظیمات گروه «{chat.title}»",
    )


@owner_only
async def restore_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Restore a chat export JSON (replied to a document)."""
    if not update.message.reply_to_message or not update.message.reply_to_message.document:
        await update.message.reply_text("❌ روی فایل خروجی ریپلای کنید.")
        return
    from bot import db
    import json
    doc = update.message.reply_to_message.document
    if not doc.file_name.endswith(".json"):
        await update.message.reply_text("❌ فقط فایل JSON پشتیبانی می‌شود.")
        return
    status = await update.message.reply_text("⏳ در حال بازگردانی...")
    try:
        f = await doc.get_file()
        bio = io.BytesIO()
        await f.download_to_memory(out=bio)
        bio.seek(0)
        data = json.loads(bio.read().decode("utf-8"))
        chat_id = int(data["chat_id"])
        conn = await db.get_conn()
        for k, v in (data.get("settings") or {}).items():
            await conn.execute(
                "INSERT OR REPLACE INTO chat_settings (chat_id, key, value) VALUES (?, ?, ?)",
                (chat_id, k, v),
            )
        for note in data.get("notes") or []:
            await conn.execute(
                "INSERT OR REPLACE INTO notes (chat_id, name, content, file_id, file_type, buttons) "
                "VALUES (?, ?, ?, ?, ?, ?)",
                (chat_id, note["name"], note.get("content"), note.get("file_id"),
                 note.get("file_type"), note.get("buttons")),
            )
        for flt in data.get("filters") or []:
            await conn.execute(
                "INSERT OR REPLACE INTO filters (chat_id, keyword, response, file_id, file_type) "
                "VALUES (?, ?, ?, ?, ?)",
                (chat_id, flt["keyword"], flt.get("response"), flt.get("file_id"),
                 flt.get("file_type")),
            )
        for lock in data.get("locks") or []:
            await conn.execute(
                "INSERT OR IGNORE INTO locks (chat_id, lock_type) VALUES (?, ?)",
                (chat_id, lock),
            )
        await conn.commit()
        await status.edit_text(f"✅ بازگردانی انجام شد. گروه: {chat_id}")
    except Exception as e:
        logger.exception("restore error: %s", e)
        try:
            await status.edit_text(f"⚠️ خطا: {e}")
        except BadRequest:
            pass


# ───────── Auto-backup commands ─────────
@owner_only
async def autobackup_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Configure auto-backup interval in hours (0 to disable)."""
    text = (update.message.text or "").split(maxsplit=1)
    if len(text) < 2:
        current = await db.get_setting(0, "auto_backup_interval", 0)
        if current:
            await update.message.reply_text(
                f"📦 بکاپ خودکار هر {current} ساعت یکبار انجام می‌شود.\n"
                f"برای تغییر: <code>autobackup N</code> (ساعت، 0 = غیرفعال)",
                parse_mode=ParseMode.HTML,
            )
        else:
            await update.message.reply_text(
                "❌ بکاپ خودکار غیرفعال است.\n"
                "فعال‌سازی: <code>autobackup N</code> (ساعت)",
                parse_mode=ParseMode.HTML,
            )
        return
    try:
        hours = int(text[1])
        if hours < 0:
            raise ValueError
        await db.set_setting(0, "auto_backup_interval", hours)
        if hours == 0:
            await update.message.reply_text("✅ بکاپ خودکار غیرفعال شد.")
        else:
            await update.message.reply_text(
                f"✅ بکاپ خودکار هر {hours} ساعت فعال شد.",
                parse_mode=ParseMode.HTML,
            )
    except (ValueError, TypeError):
        await update.message.reply_text("❌ لطفاً یک عدد معتبر (ساعت) وارد کنید.")


@sudo_only
async def backups_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """List recent backups."""
    rows = await db._fetchall(
        "SELECT filename, created_at, size_bytes FROM backups ORDER BY id DESC LIMIT 10"
    )
    if not rows:
        await update.message.reply_text("ℹ️ هیچ بکاپی یافت نشد.")
        return
    lines = []
    for r in rows:
        size = f"{r['size_bytes'] / 1024:.1f} KB" if r['size_bytes'] else "?"
        lines.append(f"• {r['filename']} ({size})")
    await update.message.reply_text(
        "📦 <b>بکاپ‌های اخیر:</b>\n" + "\n".join(lines),
        parse_mode=ParseMode.HTML,
    )


# ───────── Scheduled auto-backup job ─────────
async def _auto_backup_job(context: ContextTypes.DEFAULT_TYPE) -> None:
    """Periodic auto-backup job."""
    interval = await db.get_setting(0, "auto_backup_interval", 0)
    if interval and interval > 0:
        await _do_auto_backup()


def register_jobs(app) -> None:
    """Register the auto-backup recurring job."""
    try:
        app.job_queue.run_repeating(
            _auto_backup_job,
            interval=3600,  # check every hour
            first=600,
            name="bobi:autobackup",
        )
    except Exception as e:
        logger.warning("auto-backup job registration failed: %s", e)


# ───────── Registration ─────────
def register(app) -> None:
    register_mixed(
        app,
        {
            "backup": backup_cmd,
            "exportchat": exportchat_cmd,
            "restore": restore_cmd,
            "autobackup": autobackup_cmd,
            "backups": backups_cmd,
        },
        {
            "بکاپ": backup_cmd,
            "پشتیبان": backup_cmd,
            "بکاپ بگیر": backup_cmd,
            "خروجی گروه": exportchat_cmd,
            "خروجی تنظیمات": exportchat_cmd,
            "بازگردانی": restore_cmd,
            "بازیابی": restore_cmd,
            "بکاپ خودکار": autobackup_cmd,
            "پشتیبان خودکار": autobackup_cmd,
            "لیست بکاپ": backups_cmd,
            "بکاپ ها": backups_cmd,
        },
    )
