"""Resilience utilities — network checks, graceful degradation, DB safety."""

from __future__ import annotations

import asyncio
import logging
import sqlite3
from pathlib import Path

import aiosqlite

logger = logging.getLogger(__name__)


async def check_connectivity(timeout: float = 5.0) -> bool:
    """Check basic internet connectivity by attempting a TCP connection."""
    try:
        _, writer = await asyncio.wait_for(
            asyncio.open_connection("1.1.1.1", 80),
            timeout=timeout,
        )
        writer.close()
        await writer.wait_closed()
        return True
    except (OSError, TimeoutError):
        return False


async def check_db_integrity(db_path: str) -> dict[str, bool | str]:
    """Run SQLite integrity check on a database file.

    Returns a dict with 'ok' boolean and 'message' string.
    """
    path = Path(db_path)
    if not path.exists():
        return {"ok": False, "message": "Database file does not exist"}

    try:
        async with aiosqlite.connect(db_path) as conn:
            cursor = await conn.execute("PRAGMA integrity_check")
            row = await cursor.fetchone()
            result = row[0] if row else "unknown"
            ok = result == "ok"
            return {"ok": ok, "message": result}
    except (sqlite3.DatabaseError, aiosqlite.Error) as e:
        return {"ok": False, "message": f"Database error: {e}"}


async def attempt_db_recovery(db_path: str) -> bool:
    """Attempt to recover a corrupted SQLite database.

    Creates a new database by dumping and reimporting what's readable
    from the corrupted file.
    """
    path = Path(db_path)
    if not path.exists():
        return False

    backup_path = path.with_suffix(".db.bak")
    recovered_path = path.with_suffix(".db.recovered")

    try:
        # Rename corrupted DB
        path.rename(backup_path)

        # Try to dump readable data
        src = sqlite3.connect(str(backup_path))
        dst = sqlite3.connect(str(recovered_path))

        try:
            for line in src.iterdump():
                try:
                    dst.execute(line)
                except sqlite3.Error:
                    continue
            dst.commit()
        finally:
            src.close()
            dst.close()

        # Replace original with recovered
        recovered_path.rename(path)
        logger.info("Database recovered successfully: %s", db_path)
        return True

    except Exception:
        logger.exception("Database recovery failed for %s", db_path)
        # Restore backup if recovery failed
        if backup_path.exists() and not path.exists():
            backup_path.rename(path)
        return False


class TaskTimeout:
    """Context manager for enforcing task timeouts.

    Usage:
        async with TaskTimeout(seconds=300) as timer:
            await long_running_operation()
            if timer.remaining < 30:
                # wrap up soon
                ...
    """

    def __init__(self, seconds: float = 300.0) -> None:
        self._seconds = seconds
        self._start: float = 0.0
        self._task: asyncio.Task | None = None  # type: ignore[type-arg]

    @property
    def remaining(self) -> float:
        if self._start == 0:
            return self._seconds
        elapsed = asyncio.get_event_loop().time() - self._start
        return max(0.0, self._seconds - elapsed)

    @property
    def expired(self) -> bool:
        return self.remaining <= 0

    async def __aenter__(self) -> TaskTimeout:
        self._start = asyncio.get_event_loop().time()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> bool:  # noqa: ANN001
        return False
