from __future__ import annotations

import datetime as dt
from pathlib import Path
import shutil
import sqlite3
import tarfile
import threading
import time

from backend.chatapp.state import AppState
from backend.chatapp.util import safe_mkdir


def start_backup_loop(state: AppState) -> threading.Thread:
    def loop() -> None:
        while True:
            try:
                run_scheduled_backups(state)
            except Exception:  # noqa: BLE001
                pass
            time.sleep(max(60, int(state.config.backup_interval_seconds)))

    t = threading.Thread(target=loop, name="backup-loop", daemon=True)
    t.start()
    return t


def run_scheduled_backups(state: AppState) -> None:
    if not state.config.backup_enabled:
        return
    _ensure_daily_backup(state)
    _ensure_monthly_backup(state)
    _rotate_backups(state)


def _ensure_daily_backup(state: AppState) -> None:
    today = dt.date.today().isoformat()
    daily_dir = state.backups_dir / "daily"
    safe_mkdir(daily_dir)
    out_path = daily_dir / f"daily-{today}.tar.gz"
    if out_path.exists():
        return
    _create_backup_tar(state, out_path)


def _ensure_monthly_backup(state: AppState) -> None:
    month = dt.date.today().strftime("%Y-%m")
    monthly_dir = state.backups_dir / "monthly"
    safe_mkdir(monthly_dir)
    out_path = monthly_dir / f"monthly-{month}.tar.gz"
    if out_path.exists():
        return
    _create_backup_tar(state, out_path)


def _create_backup_tar(state: AppState, out_path: Path) -> None:
    tmp_path = out_path.with_suffix(out_path.suffix + ".tmp")
    tmp_path.unlink(missing_ok=True)
    safe_mkdir(out_path.parent)

    snapshot_db = out_path.parent / f"db-snapshot-{int(time.time())}.sqlite"
    snapshot_db.unlink(missing_ok=True)
    _sqlite_snapshot(state.db_path, snapshot_db)

    try:
        with tarfile.open(tmp_path, "w:gz") as tar:
            tar.add(snapshot_db, arcname="db/app.db")
            if state.config.backup_include_storage and state.storage_dir.exists():
                tar.add(state.storage_dir, arcname="storage")
    finally:
        snapshot_db.unlink(missing_ok=True)

    tmp_path.replace(out_path)


def _sqlite_snapshot(src_db: Path, dst_db: Path) -> None:
    src = sqlite3.connect(str(src_db))
    try:
        dst = sqlite3.connect(str(dst_db))
        try:
            src.backup(dst)
        finally:
            dst.close()
    finally:
        src.close()


def _rotate_backups(state: AppState) -> None:
    daily_keep = max(1, int(state.config.backup_keep_daily))
    monthly_keep = max(1, int(state.config.backup_keep_monthly))

    daily_dir = state.backups_dir / "daily"
    monthly_dir = state.backups_dir / "monthly"
    _rotate_dir(daily_dir, prefix="daily-", keep=daily_keep)
    _rotate_dir(monthly_dir, prefix="monthly-", keep=monthly_keep)


def _rotate_dir(directory: Path, *, prefix: str, keep: int) -> None:
    if not directory.exists():
        return
    items = sorted((p for p in directory.glob(f"{prefix}*.tar.gz") if p.is_file()), key=lambda p: p.stat().st_mtime, reverse=True)
    for p in items[keep:]:
        try:
            p.unlink()
        except Exception:  # noqa: BLE001
            pass

