# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Runtime dependency installer for feature activation."""

import importlib
import logging
import subprocess
import sys
from typing import Callable, List, Optional, Tuple

logger = logging.getLogger(__name__)

# ── Package group constants ──────────────────────────────────────────

VOICE_STT_PACKAGES = [("faster_whisper", "faster-whisper")]
VOICE_TTS_PACKAGES = [("piper", "piper-tts")]
BROWSER_PACKAGES = [("playwright", "playwright")]
GOOGLE_PACKAGES = [
    ("google_auth_oauthlib", "google-auth-oauthlib"),
    ("google_auth_httplib2", "google-auth-httplib2"),
    ("googleapiclient", "google-api-python-client"),
]
CALDAV_PACKAGES = [("caldav", "caldav"), ("vobject", "vobject")]
SMS_PACKAGES = [("twilio", "twilio")]
KNOWLEDGE_PACKAGES = [("fastembed", "fastembed")]
AIOSMTPD_PACKAGES = [("aiosmtpd", "aiosmtpd")]
PROTONVPN_PACKAGES = [("protonvpn_cli", "protonvpn-cli")]
PDF_PACKAGES = [("fitz", "PyMuPDF")]
DOCX_PACKAGES = [("docx", "python-docx")]
XLSX_PACKAGES = [("openpyxl", "openpyxl")]
OCR_PACKAGES = [("pytesseract", "pytesseract"), ("PIL", "Pillow")]
WEASYPRINT_PACKAGES = [("weasyprint", "weasyprint")]
WHISPER_PACKAGES = [("faster_whisper", "faster-whisper")]
PYAUDIO_PACKAGES = [("pyaudio", "pyaudio")]
MATRIX_PACKAGES = [("nio", "matrix-nio")]
YTDLP_PACKAGES = [("yt_dlp", "yt-dlp")]
GITHUB_PACKAGES = [("github", "PyGithub")]
SLACK_PACKAGES = [("slack_sdk", "slack-sdk")]


def ensure_packages(
    packages: List[Tuple[str, str]],
    notify: Optional[Callable[[str], None]] = None,
    timeout: int = 300,
) -> Tuple[bool, List[str]]:
    """
    Check and auto-install missing packages.

    Args:
        packages: List of (import_name, pip_name) tuples.
        notify: Optional callback for progress messages (e.g. send to Telegram).
        timeout: pip install timeout in seconds.

    Returns:
        (all_ok, list_of_failed_pip_names)
    """
    missing = []
    for import_name, pip_name in packages:
        try:
            importlib.import_module(import_name)
        except ImportError:
            missing.append(pip_name)

    if not missing:
        return True, []

    if notify:
        notify(f"Installing {', '.join(missing)}...")

    logger.info(f"Auto-installing: {' '.join(missing)}")

    try:
        result = subprocess.run(
            [sys.executable, "-m", "pip", "install", "-q", "--break-system-packages"] + missing,
            capture_output=True,
            text=True,
            timeout=timeout,
        )
        if result.returncode == 0:
            importlib.invalidate_caches()
            if notify:
                notify("Installation complete.")
            logger.info(f"Installed: {' '.join(missing)}")
            return True, []
        else:
            logger.warning(f"pip install failed: {result.stderr}")
            if notify:
                notify(f"Installation failed: {result.stderr[:200]}")
            return False, missing
    except subprocess.TimeoutExpired:
        logger.warning(f"pip install timed out after {timeout}s")
        if notify:
            notify("Installation timed out.")
        return False, missing
    except Exception as e:
        logger.warning(f"pip install error: {e}")
        if notify:
            notify(f"Installation error: {e}")
        return False, missing


async def ensure_packages_async(
    packages: List[Tuple[str, str]],
    notify: Optional[Callable] = None,
    timeout: int = 300,
) -> Tuple[bool, List[str]]:
    """Async wrapper -- runs pip in a thread to avoid blocking the event loop."""
    import asyncio

    return await asyncio.to_thread(ensure_packages, packages, notify, timeout)
