"""Browser profile detection and management for VM isolation.

Detects Firefox, Chrome, and Chromium profiles on the host, extracts
metadata (size, active sessions, detected accounts), and provides
utilities for copying profiles into VMs.

Usage:
    profiles = detect_all_profiles()
    for p in profiles:
        print(f"{p.browser}/{p.name}: {p.size_mb:.1f}MB, sessions={p.has_sessions}")
"""

from __future__ import annotations

import configparser
import json
import sqlite3
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import structlog

logger = structlog.get_logger()

# Domains to scan for in cookies to detect logged-in accounts
ACCOUNT_DOMAINS = [
    "anthropic.com",
    "openrouter.ai",
    "cursor.sh",
    "windsurf.ai",
    "github.com",
    "openai.com",
    "google.com",
    "accounts.google.com",
    "huggingface.co",
    "console.cloud.google.com",
]

# Files to include when copying a Firefox profile (exclude cache, logs)
FIREFOX_INCLUDE = [
    "cookies.sqlite",
    "webappsstore.sqlite",
    "storage/",
    "extensions/",
    "prefs.js",
    "logins.json",
    "key4.db",
    "cert9.db",
    "permissions.sqlite",
    "formhistory.sqlite",
]

FIREFOX_EXCLUDE = [
    "cache2/",
    "startupCache/",
    "safebrowsing/",
    "*.log",
    "datareporting/",
    "crashes/",
    "minidumps/",
    "saved-telemetry-pings/",
]

# Files to include when copying a Chrome/Chromium profile
CHROME_INCLUDE = [
    "Cookies",
    "Local Storage/",
    "Session Storage/",
    "Extensions/",
    "Preferences",
    "Login Data",
    "Web Data",
    "Bookmarks",
]

CHROME_EXCLUDE = [
    "Cache/",
    "Code Cache/",
    "GPUCache/",
    "Service Worker/",
    "*.log",
    "BrowserMetrics/",
    "blob_storage/",
]


@dataclass
class BrowserProfile:
    """Detected browser profile on the host."""
    browser: str           # "firefox" | "chrome" | "chromium"
    name: str              # "default" | "Profile 1" | etc.
    path: Path             # absolute path to profile directory
    size_mb: float = 0.0
    has_sessions: bool = False     # whether active sessions/cookies exist
    accounts_detected: list[str] = field(default_factory=list)  # detected login domains

    def to_dict(self) -> dict[str, Any]:
        """Serialize to dict for JSON/API responses."""
        return {
            "browser": self.browser,
            "name": self.name,
            "path": str(self.path),
            "size_mb": round(self.size_mb, 1),
            "has_sessions": self.has_sessions,
            "accounts_detected": self.accounts_detected,
        }


@dataclass
class AppDataSync:
    """Configuration for syncing browser data into a VM."""
    browser: str
    source: str          # host path
    target: str          # guest path
    include: list[str] = field(default_factory=list)
    exclude: list[str] = field(default_factory=list)
    with_passwords: bool = False  # if False, skip key4.db / Login Data


# ── Detection ────────────────────────────────────────────────

def detect_firefox_profiles() -> list[BrowserProfile]:
    """Detect Firefox profiles by parsing profiles.ini."""
    profiles = []
    ini_path = Path.home() / ".mozilla" / "firefox" / "profiles.ini"
    if not ini_path.exists():
        return profiles

    config = configparser.ConfigParser()
    try:
        config.read(str(ini_path))
    except configparser.Error as e:
        logger.warning("firefox_profiles_ini_parse_error", error=str(e))
        return profiles

    base_dir = ini_path.parent
    for section in config.sections():
        if not section.startswith("Profile"):
            continue
        name = config.get(section, "Name", fallback="unknown")
        path_str = config.get(section, "Path", fallback="")
        is_relative = config.getboolean(section, "IsRelative", fallback=True)

        if not path_str:
            continue

        if is_relative:
            profile_path = base_dir / path_str
        else:
            profile_path = Path(path_str)

        if not profile_path.is_dir():
            continue

        profile = BrowserProfile(
            browser="firefox",
            name=name,
            path=profile_path,
            size_mb=_dir_size_mb(profile_path),
        )

        # Check for sessions via cookies.sqlite
        cookies_db = profile_path / "cookies.sqlite"
        if cookies_db.exists():
            profile.has_sessions = True
            profile.accounts_detected = _scan_firefox_cookies(cookies_db)

        profiles.append(profile)

    return profiles


def detect_all_profiles() -> list[BrowserProfile]:
    """Detect all browser profiles on the host."""
    from proxym.virt.utils.detect_chrome_profiles import detect_chrome_profiles, detect_chromium_profiles
    
    profiles: list[BrowserProfile] = []
    profiles.extend(detect_firefox_profiles())
    profiles.extend(detect_chrome_profiles())
    profiles.extend(detect_chromium_profiles())
    return profiles


def detect_chrome_profiles() -> list[BrowserProfile]:
    """Detect Chrome browser profiles."""
    from proxym.virt.utils.detect_chrome_profiles import detect_chrome_profiles as _detect_chrome_profiles
    return _detect_chrome_profiles()


def detect_chromium_profiles() -> list[BrowserProfile]:
    """Detect Chromium browser profiles."""
    from proxym.virt.utils.detect_chrome_profiles import detect_chromium_profiles as _detect_chromium_profiles
    return _detect_chromium_profiles()


# ── App Data Sync Config Generation ──────────────────────────

def generate_app_data_sync(profile: BrowserProfile,
                           with_passwords: bool = False) -> AppDataSync:
    """Generate an AppDataSync config for copying a profile into a VM.

    By default, key4.db (Firefox) and Login Data (Chrome) are excluded
    unless with_passwords=True.
    """
    if profile.browser == "firefox":
        include = list(FIREFOX_INCLUDE)
        exclude = list(FIREFOX_EXCLUDE)
        target = "/home/dev/.mozilla/firefox/cloned-profile"
        if not with_passwords:
            include = [f for f in include if f not in ("key4.db", "logins.json")]
    else:
        include = list(CHROME_INCLUDE)
        exclude = list(CHROME_EXCLUDE)
        target = f"/home/dev/.config/{_chrome_config_dir(profile.browser)}/Default"
        if not with_passwords:
            include = [f for f in include if f != "Login Data"]

    return AppDataSync(
        browser=profile.browser,
        source=str(profile.path),
        target=target,
        include=include,
        exclude=exclude,
        with_passwords=with_passwords,
    )


# ── Internals ────────────────────────────────────────────────

def _detect_chromium_family(base_dir: Path, browser: str) -> list[BrowserProfile]:
    """Detect profiles for Chrome/Chromium from Local State file."""
    profiles = []
    local_state = base_dir / "Local State"
    if not local_state.exists():
        return profiles

    try:
        data = json.loads(local_state.read_text())
    except (json.JSONDecodeError, OSError) as e:
        logger.warning("chrome_local_state_parse_error", browser=browser, error=str(e))
        return profiles

    profile_info = data.get("profile", {}).get("info_cache", {})
    if not profile_info:
        # Fallback: check for Default profile directory
        default_dir = base_dir / "Default"
        if default_dir.is_dir():
            profile_info = {"Default": {"name": "Default"}}

    for dir_name, info in profile_info.items():
        profile_path = base_dir / dir_name
        if not profile_path.is_dir():
            continue

        display_name = info.get("name", dir_name) if isinstance(info, dict) else dir_name
        profile = BrowserProfile(
            browser=browser,
            name=display_name,
            path=profile_path,
            size_mb=_dir_size_mb(profile_path),
        )

        cookies_file = profile_path / "Cookies"
        if cookies_file.exists():
            profile.has_sessions = True
            profile.accounts_detected = _scan_chrome_cookies(cookies_file)

        profiles.append(profile)

    return profiles


def _scan_cookies(db_path: Path, table: str, column: str, label: str) -> list[str]:
    """Scan a SQLite cookies DB for known account domains."""
    found = []
    try:
        conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
        cursor = conn.cursor()
        for domain in ACCOUNT_DOMAINS:
            cursor.execute(
                f"SELECT COUNT(*) FROM {table} WHERE {column} LIKE ?",  # noqa: S608
                (f"%{domain}%",),
            )
            if cursor.fetchone()[0] > 0:
                found.append(domain)
        conn.close()
    except (sqlite3.Error, OSError) as e:
        logger.debug(f"{label}_cookies_scan_error", db=str(db_path), error=str(e))
    return found


def _scan_firefox_cookies(cookies_db: Path) -> list[str]:
    """Scan Firefox cookies.sqlite for known account domains."""
    return _scan_cookies(cookies_db, "moz_cookies", "host", "firefox")


def _scan_chrome_cookies(cookies_file: Path) -> list[str]:
    """Scan Chrome/Chromium Cookies file for known account domains."""
    return _scan_cookies(cookies_file, "cookies", "host_key", "chrome")


def _dir_size_mb(path: Path) -> float:
    """Calculate directory size in MB (non-recursive for speed, top-level files only)."""
    try:
        total = sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
        return total / (1024 * 1024)
    except OSError:
        return 0.0


def _chrome_config_dir(browser: str) -> str:
    """Return the config directory name for a Chromium-family browser."""
    return "google-chrome" if browser == "chrome" else "chromium"
