# 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

"""
Pi-hole / AdGuard Home Skill

Monitor blocking statistics, view query logs, and toggle DNS-level blocking.
Supports both Pi-hole and AdGuard Home backends.

Setup:
    PIHOLE_URL=http://192.168.1.1
    PIHOLE_TOKEN=your-api-token
    PIHOLE_TYPE=pihole          # or "adguard"
"""

import logging
import os

import httpx

from familiar.core.utils import format_http_error

logger = logging.getLogger(__name__)

REQUEST_TIMEOUT = 30


# ---------------------------------------------------------------------------
# Configuration helpers
# ---------------------------------------------------------------------------


def _get_pihole_config() -> dict:
    return {
        "url": os.environ.get("PIHOLE_URL", "").rstrip("/"),
        "token": os.environ.get("PIHOLE_TOKEN", ""),
        "type": os.environ.get("PIHOLE_TYPE", "pihole").lower(),
    }


def _pihole_configured() -> bool:
    cfg = _get_pihole_config()
    return bool(cfg["url"] and cfg["token"])


def _pihole_request(
    endpoint: str,
    params: dict | None = None,
    method: str = "GET",
    json_body: dict | None = None,
) -> httpx.Response:
    """Send a request to the Pi-hole or AdGuard Home API.

    For Pi-hole: auth is via ``?auth={token}`` query parameter.
    For AdGuard: auth is via HTTP Basic Auth (token as ``username:password``).
    """
    cfg = _get_pihole_config()
    url = f"{cfg['url']}{endpoint}"

    if cfg["type"] == "adguard":
        # AdGuard uses basic auth; token is "user:password"
        parts = cfg["token"].split(":", 1)
        auth = (parts[0], parts[1]) if len(parts) == 2 else (cfg["token"], "")
        if method == "POST":
            return httpx.post(url, auth=auth, json=json_body, timeout=REQUEST_TIMEOUT)
        return httpx.get(url, auth=auth, params=params, timeout=REQUEST_TIMEOUT)
    else:
        # Pi-hole uses auth query parameter
        if params is None:
            params = {}
        params["auth"] = cfg["token"]
        if method == "POST":
            return httpx.post(url, params=params, json=json_body, timeout=REQUEST_TIMEOUT)
        return httpx.get(url, params=params, timeout=REQUEST_TIMEOUT)


# ---------------------------------------------------------------------------
# Tool handlers
# ---------------------------------------------------------------------------


def pihole_get_stats(data: dict) -> str:
    """Get blocking statistics summary."""
    if not _pihole_configured():
        return "Pi-hole/AdGuard not configured. Run /connect pihole to set up, or set PIHOLE_URL and PIHOLE_TOKEN."

    cfg = _get_pihole_config()

    try:
        if cfg["type"] == "adguard":
            resp = _pihole_request("/control/stats")
        else:
            resp = _pihole_request("/admin/api.php", params={"summary": ""})

        if resp.status_code != 200:
            return format_http_error("Pi-hole", status_code=resp.status_code, connect_cmd="pihole")

        stats = resp.json()

        if cfg["type"] == "adguard":
            total = stats.get("num_dns_queries", 0)
            blocked = stats.get("num_blocked_filtering", 0)
            pct = (blocked / total * 100) if total else 0
            avg_ms = stats.get("avg_processing_time", 0)
            avg_ms_str = f"{avg_ms * 1000:.1f} ms" if avg_ms else "N/A"
            lines = [
                "AdGuard Home Statistics:",
                f"  Total queries: {total:,}",
                f"  Blocked: {blocked:,} ({pct:.1f}%)",
                f"  Avg. processing time: {avg_ms_str}",
            ]
        else:
            total = stats.get("dns_queries_today", 0)
            blocked = stats.get("ads_blocked_today", 0)
            pct = stats.get("ads_percentage_today", 0)
            domains = stats.get("domains_being_blocked", 0)
            unique_domains = stats.get("unique_domains", 0)
            lines = [
                "Pi-hole Statistics:",
                f"  Total queries today: {total:,}",
                f"  Blocked today: {blocked:,} ({pct:.1f}%)",
                f"  Domains on blocklist: {domains:,}",
                f"  Unique domains queried: {unique_domains:,}",
            ]

        return "\n".join(lines)

    except httpx.HTTPError as e:
        logger.error(f"Pi-hole stats error: {e}")
        return format_http_error("Pi-hole", exception=e, connect_cmd="pihole")


def pihole_top_queries(data: dict) -> str:
    """Show top queries and blocked domains."""
    if not _pihole_configured():
        return "Pi-hole/AdGuard not configured. Run /connect pihole to set up, or set PIHOLE_URL and PIHOLE_TOKEN."

    cfg = _get_pihole_config()
    count = data.get("count", 10)

    try:
        if cfg["type"] == "adguard":
            resp = _pihole_request("/control/querylog", params={"limit": str(count)})
        else:
            resp = _pihole_request("/admin/api.php", params={"topItems": str(count)})

        if resp.status_code != 200:
            return format_http_error("Pi-hole", status_code=resp.status_code, connect_cmd="pihole")

        result = resp.json()

        if cfg["type"] == "adguard":
            entries = result.get("data", [])
            if not entries:
                return "No recent queries."

            # Tally top queried domains
            domain_counts: dict[str, int] = {}
            blocked_counts: dict[str, int] = {}
            for entry in entries:
                question = entry.get("question", {})
                domain = question.get("name", "unknown")
                domain_counts[domain] = domain_counts.get(domain, 0) + 1
                reason = entry.get("reason", "")
                if "Filtered" in reason or "Blocked" in reason:
                    blocked_counts[domain] = blocked_counts.get(domain, 0) + 1

            top_queried = sorted(domain_counts.items(), key=lambda x: x[1], reverse=True)[:count]
            top_blocked = sorted(blocked_counts.items(), key=lambda x: x[1], reverse=True)[:count]

            lines = ["Top queries:"]
            for domain, cnt in top_queried:
                lines.append(f"  {cnt:>5}  {domain}")

            if top_blocked:
                lines.append("\nTop blocked:")
                for domain, cnt in top_blocked:
                    lines.append(f"  {cnt:>5}  {domain}")

        else:
            top_queries = result.get("top_queries", {})
            top_ads = result.get("top_ads", {})

            lines = ["Top queries:"]
            for domain, cnt in sorted(top_queries.items(), key=lambda x: x[1], reverse=True)[
                :count
            ]:
                lines.append(f"  {cnt:>5}  {domain}")

            if top_ads:
                lines.append("\nTop blocked:")
                for domain, cnt in sorted(top_ads.items(), key=lambda x: x[1], reverse=True)[
                    :count
                ]:
                    lines.append(f"  {cnt:>5}  {domain}")

        return "\n".join(lines)

    except httpx.HTTPError as e:
        logger.error(f"Pi-hole top queries error: {e}")
        return format_http_error("Pi-hole", exception=e, connect_cmd="pihole")


def pihole_toggle_blocking(data: dict) -> str:
    """Enable or disable blocking."""
    if not _pihole_configured():
        return "Pi-hole/AdGuard not configured. Run /connect pihole to set up, or set PIHOLE_URL and PIHOLE_TOKEN."

    enable = data.get("enable", True)
    cfg = _get_pihole_config()

    try:
        if cfg["type"] == "adguard":
            resp = _pihole_request(
                "/control/dns_config",
                method="POST",
                json_body={"protection_enabled": enable},
            )
        else:
            action = "enable" if enable else "disable"
            resp = _pihole_request("/admin/api.php", params={action: ""})

        if resp.status_code != 200:
            return format_http_error("Pi-hole", status_code=resp.status_code, connect_cmd="pihole")

        state = "enabled" if enable else "disabled"
        backend = "AdGuard Home" if cfg["type"] == "adguard" else "Pi-hole"
        return f"{backend} blocking {state}."

    except httpx.HTTPError as e:
        logger.error(f"Pi-hole toggle error: {e}")
        return format_http_error("Pi-hole", exception=e, connect_cmd="pihole")


def pihole_get_status(data: dict) -> str:
    """Check if blocking is enabled."""
    if not _pihole_configured():
        return "Pi-hole/AdGuard not configured. Run /connect pihole to set up, or set PIHOLE_URL and PIHOLE_TOKEN."

    cfg = _get_pihole_config()

    try:
        if cfg["type"] == "adguard":
            resp = _pihole_request("/control/status")
        else:
            resp = _pihole_request("/admin/api.php", params={"status": ""})

        if resp.status_code != 200:
            return format_http_error("Pi-hole", status_code=resp.status_code, connect_cmd="pihole")

        result = resp.json()

        if cfg["type"] == "adguard":
            protection = result.get("protection_enabled", False)
            running = result.get("running", False)
            version = result.get("version", "unknown")
            status = "enabled" if protection else "DISABLED"
            lines = [
                "AdGuard Home Status:",
                f"  Running: {'yes' if running else 'no'}",
                f"  Protection: {status}",
                f"  Version: {version}",
            ]
        else:
            status = result.get("status", "unknown")
            lines = [
                "Pi-hole Status:",
                f"  Blocking: {status}",
            ]

        return "\n".join(lines)

    except httpx.HTTPError as e:
        logger.error(f"Pi-hole status error: {e}")
        return format_http_error("Pi-hole", exception=e, connect_cmd="pihole")


def pihole_query_log(data: dict) -> str:
    """View recent DNS queries."""
    if not _pihole_configured():
        return "Pi-hole/AdGuard not configured. Run /connect pihole to set up, or set PIHOLE_URL and PIHOLE_TOKEN."

    cfg = _get_pihole_config()
    count = data.get("count", 100)

    try:
        if cfg["type"] == "adguard":
            resp = _pihole_request("/control/querylog", params={"limit": str(count)})
        else:
            resp = _pihole_request("/admin/api.php", params={"getAllQueries": str(count)})

        if resp.status_code != 200:
            return format_http_error("Pi-hole", status_code=resp.status_code, connect_cmd="pihole")

        result = resp.json()

        if cfg["type"] == "adguard":
            entries = result.get("data", [])
            if not entries:
                return "No recent queries."

            lines = [f"Recent queries ({len(entries)}):"]
            for entry in entries[:50]:
                question = entry.get("question", {})
                domain = question.get("name", "unknown")
                qtype = question.get("type", "")
                reason = entry.get("reason", "")
                blocked = "BLOCKED" if ("Filtered" in reason or "Blocked" in reason) else "OK"
                time_str = entry.get("time", "")[:19]
                lines.append(f"  {time_str}  {blocked:<7}  {qtype:<5}  {domain}")
        else:
            queries = result.get("data", [])
            if not queries:
                return "No recent queries."

            lines = [f"Recent queries ({len(queries)}):"]
            for q in queries[:50]:
                if isinstance(q, list) and len(q) >= 4:
                    timestamp = q[0]
                    qtype = q[1]
                    domain = q[2]
                    status = q[3]
                    blocked = "BLOCKED" if status in ("1", "4", "5", "9", "10", "11") else "OK"
                    lines.append(f"  {timestamp}  {blocked:<7}  {qtype:<5}  {domain}")

        if len(lines) > 51:
            lines = lines[:51]
            lines.append(f"  ... (showing first 50 of {count})")

        return "\n".join(lines)

    except httpx.HTTPError as e:
        logger.error(f"Pi-hole query log error: {e}")
        return format_http_error("Pi-hole", exception=e, connect_cmd="pihole")


# ---------------------------------------------------------------------------
# Tool definitions
# ---------------------------------------------------------------------------

TOOLS = [
    {
        "name": "pihole_get_stats",
        "description": (
            "Get blocking statistics summary from Pi-hole or AdGuard Home including total queries, blocked count, and percentage. "
            "For Pi-hole, also shows domains on blocklist and unique domains queried. "
            "For AdGuard Home, shows average processing time. "
            "Use pihole_get_status for a simple enabled/disabled check instead."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": pihole_get_stats,
        "category": "pihole",
    },
    {
        "name": "pihole_top_queries",
        "description": (
            "Show the most frequently queried and most frequently blocked domains from Pi-hole or AdGuard Home. "
            "Returns two ranked lists: top queries by volume and top blocked domains. "
            "Use the count parameter to control how many entries appear in each list."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "count": {
                    "type": "integer",
                    "description": "Number of top items to show",
                    "default": 10,
                },
            },
        },
        "handler": pihole_top_queries,
        "category": "pihole",
    },
    {
        "name": "pihole_toggle_blocking",
        "description": (
            "Enable or disable DNS-level ad and tracker blocking on Pi-hole or AdGuard Home. "
            "This action requires user confirmation before execution due to its network-wide impact. "
            "Set enable to true to turn blocking on, or false to turn it off. "
            "Supports both Pi-hole API and AdGuard Home API backends."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "enable": {
                    "type": "boolean",
                    "description": "True to enable blocking, false to disable",
                },
            },
            "required": ["enable"],
        },
        "handler": pihole_toggle_blocking,
        "confirm": True,
        "confirm_message": "Toggle DNS blocking on Pi-hole/AdGuard Home?",
        "risk_level": "high",
        "category": "pihole",
    },
    {
        "name": "pihole_get_status",
        "description": (
            "Check whether DNS blocking is currently enabled or disabled on Pi-hole or AdGuard Home. "
            "For AdGuard Home, also shows running state and version number. "
            "For detailed statistics like query counts and percentages, use pihole_get_stats instead."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": pihole_get_status,
        "category": "pihole",
    },
    {
        "name": "pihole_query_log",
        "description": (
            "View recent DNS queries from Pi-hole or AdGuard Home with blocked/allowed status. "
            "Shows timestamp, block status, query type, and domain for each entry. "
            "Displays up to 50 entries from the requested count. "
            "Use this to troubleshoot blocked domains or monitor DNS activity."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "count": {
                    "type": "integer",
                    "description": "Number of recent queries to show",
                    "default": 100,
                },
            },
        },
        "handler": pihole_query_log,
        "category": "pihole",
    },
]
