# 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

"""
CrowdSec Skill

Manage CrowdSec decisions (bans/captchas), review threat intelligence alerts,
and manually add or remove IP blocks.

Setup:
    CROWDSEC_URL=http://localhost:8080       # CrowdSec Local API
    CROWDSEC_API_KEY=your-lapi-key           # Generate with: cscli bouncers add familiar
"""

import logging
import os

import httpx

from familiar.core.utils import format_http_error

logger = logging.getLogger(__name__)

REQUEST_TIMEOUT = 20


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


def _get_config() -> dict:
    return {
        "url": os.environ.get("CROWDSEC_URL", "http://localhost:8080").rstrip("/"),
        "api_key": os.environ.get("CROWDSEC_API_KEY", ""),
    }


def _crowdsec_available() -> bool:
    cfg = _get_config()
    return bool(cfg["url"] and cfg["api_key"])


def _api_get(path: str, params: dict | None = None) -> httpx.Response:
    cfg = _get_config()
    return httpx.get(
        f"{cfg['url']}{path}",
        headers={"Authorization": f"ApiKey {cfg['api_key']}"},
        params=params or {},
        timeout=REQUEST_TIMEOUT,
    )


def _api_post(path: str, json_body: dict) -> httpx.Response:
    cfg = _get_config()
    return httpx.post(
        f"{cfg['url']}{path}",
        headers={"Authorization": f"ApiKey {cfg['api_key']}"},
        json=json_body,
        timeout=REQUEST_TIMEOUT,
    )


def _api_delete(path: str, params: dict | None = None) -> httpx.Response:
    cfg = _get_config()
    return httpx.delete(
        f"{cfg['url']}{path}",
        headers={"Authorization": f"ApiKey {cfg['api_key']}"},
        params=params or {},
        timeout=REQUEST_TIMEOUT,
    )


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


def get_crowdsec_decisions(data: dict) -> str:
    """List active CrowdSec decisions (bans, captchas, throttles)."""
    if not _crowdsec_available():
        return (
            "CrowdSec not configured. Set CROWDSEC_URL and CROWDSEC_API_KEY. "
            "Generate a key with: sudo cscli bouncers add familiar"
        )

    decision_type = data.get("type", "").lower()
    params: dict = {"limit": min(int(data.get("limit", 50)), 200)}
    if decision_type:
        params["type"] = decision_type

    try:
        resp = _api_get("/v1/decisions", params=params)
        if resp.status_code == 404:
            return "No active CrowdSec decisions. Network is clean."
        if resp.status_code != 200:
            return format_http_error("CrowdSec", status_code=resp.status_code)

        decisions = resp.json() or []
        if not decisions:
            return "No active CrowdSec decisions."

        ban_count = sum(1 for d in decisions if d.get("type") == "ban")
        captcha_count = sum(1 for d in decisions if d.get("type") == "captcha")

        lines = [
            f"Active CrowdSec decisions: {len(decisions)} total "
            f"({ban_count} bans, {captcha_count} captchas)",
            "",
        ]
        for d in decisions[:50]:
            did = d.get("id", "?")
            dtype = d.get("type", "?")
            value = d.get("value", "?")
            origin = d.get("origin", "?")
            reason = d.get("scenario", d.get("reason", "?"))
            duration = d.get("duration", "?")
            lines.append(
                f"  [{did:6}] {dtype:<8} {value:<20} origin={origin:<12} "
                f"{reason[:40]:<42} dur={duration}"
            )

        if len(decisions) > 50:
            lines.append(f"  ... and {len(decisions) - 50} more")

        return "\n".join(lines)

    except httpx.HTTPError as e:
        return format_http_error("CrowdSec", exception=e)


def get_crowdsec_alerts(data: dict) -> str:
    """View recent CrowdSec alerts (threat detection events)."""
    if not _crowdsec_available():
        return "CrowdSec not configured. Set CROWDSEC_URL and CROWDSEC_API_KEY."

    limit = min(int(data.get("limit", 20)), 100)

    try:
        resp = _api_get("/v1/alerts", params={"limit": limit})
        if resp.status_code != 200:
            return format_http_error("CrowdSec", status_code=resp.status_code)

        alerts = resp.json() or []
        if not alerts:
            return "No recent CrowdSec alerts."

        lines = [f"Recent CrowdSec alerts ({len(alerts)} shown):"]
        for alert in alerts:
            ts = (alert.get("created_at") or "")[:19]
            scenario = alert.get("scenario", "?")
            src = alert.get("source", {})
            ip = src.get("ip", src.get("value", "?"))
            country = src.get("cn", "??")
            events_count = alert.get("events_count", 0)
            lines.append(
                f"  {ts}  {ip:<18} [{country}]  {scenario[:50]:<52} ({events_count} events)"
            )

        return "\n".join(lines)

    except httpx.HTTPError as e:
        return format_http_error("CrowdSec", exception=e)


def add_crowdsec_decision(data: dict) -> str:
    """Manually add a CrowdSec ban or captcha decision for an IP."""
    if not _crowdsec_available():
        return "CrowdSec not configured. Set CROWDSEC_URL and CROWDSEC_API_KEY."

    ip = data.get("ip", "").strip()
    if not ip:
        return "ip is required."

    decision_type = data.get("type", "ban")
    duration = data.get("duration", "24h")
    reason = data.get("reason", "Manual block via Familiar")

    if decision_type not in ("ban", "captcha", "throttle"):
        return f"Invalid type '{decision_type}'. Use: ban, captcha, throttle."

    body = [
        {
            "capacity": 0,
            "decisions": [
                {
                    "duration": duration,
                    "origin": "familiar",
                    "scenario": reason,
                    "scope": "Ip",
                    "type": decision_type,
                    "value": ip,
                }
            ],
            "events": [],
            "labels": {"origin": "familiar"},
            "message": reason,
            "scenario": reason,
            "scenario_hash": "",
            "scenario_version": "",
            "simulated": False,
            "source": {"ip": ip, "scope": "Ip", "value": ip},
            "start_at": "",
            "stop_at": "",
        }
    ]

    try:
        resp = _api_post("/v1/alerts", body)
        if resp.status_code in (200, 201):
            return f"Decision added: {decision_type} {ip} for {duration} — {reason}"
        return format_http_error("CrowdSec", status_code=resp.status_code)

    except httpx.HTTPError as e:
        return format_http_error("CrowdSec", exception=e)


def delete_crowdsec_decision(data: dict) -> str:
    """Remove a CrowdSec decision (unban/uncaptcha) by ID or IP."""
    if not _crowdsec_available():
        return "CrowdSec not configured. Set CROWDSEC_URL and CROWDSEC_API_KEY."

    decision_id = data.get("id")
    ip = data.get("ip", "").strip()

    if not decision_id and not ip:
        return "Provide either 'id' (decision ID) or 'ip' to remove a decision."

    try:
        if decision_id:
            resp = _api_delete(f"/v1/decisions/{decision_id}")
        else:
            resp = _api_delete("/v1/decisions", params={"value": ip})

        if resp.status_code in (200, 204):
            target = f"decision {decision_id}" if decision_id else f"decisions for {ip}"
            return f"Removed {target} from CrowdSec."

        if resp.status_code == 404:
            return "Decision not found — it may have already expired or been removed."

        return format_http_error("CrowdSec", status_code=resp.status_code)

    except httpx.HTTPError as e:
        return format_http_error("CrowdSec", exception=e)


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

TOOLS = [
    {
        "name": "get_crowdsec_decisions",
        "description": (
            "List active CrowdSec decisions — current bans, captchas, and throttles on IP addresses. "
            "Filter by type (ban, captcha) or omit for all. Shows decision ID, type, IP, origin, "
            "reason, and duration. Use this to review what's currently blocked before unbanning."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string",
                    "enum": ["ban", "captcha", "throttle", ""],
                    "description": "Filter by decision type. Omit for all.",
                },
                "limit": {
                    "type": "integer",
                    "description": "Max decisions to return (default 50)",
                    "default": 50,
                },
            },
        },
        "handler": get_crowdsec_decisions,
        "category": "crowdsec",
    },
    {
        "name": "get_crowdsec_alerts",
        "description": (
            "View recent CrowdSec threat detection alerts — events that triggered decisions. "
            "Shows timestamp, source IP, country, detection scenario, and event count. "
            "Use this to understand what attacks are being detected and blocked."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "limit": {
                    "type": "integer",
                    "description": "Max alerts to return (default 20)",
                    "default": 20,
                },
            },
        },
        "handler": get_crowdsec_alerts,
        "category": "crowdsec",
    },
    {
        "name": "add_crowdsec_decision",
        "description": (
            "Manually add a CrowdSec ban, captcha, or throttle decision for an IP address. "
            "Use this to block a specific IP that isn't being caught automatically. "
            "Specify duration (e.g. '24h', '7d', '1w') and a reason for the audit trail."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "ip": {"type": "string", "description": "IP address or CIDR range to block"},
                "type": {
                    "type": "string",
                    "enum": ["ban", "captcha", "throttle"],
                    "description": "Decision type",
                    "default": "ban",
                },
                "duration": {
                    "type": "string",
                    "description": "Duration string (e.g. '24h', '7d')",
                    "default": "24h",
                },
                "reason": {
                    "type": "string",
                    "description": "Reason for the block (logged in audit trail)",
                },
            },
            "required": ["ip"],
        },
        "handler": add_crowdsec_decision,
        "confirm": True,
        "confirm_message": "Add CrowdSec block decision?",
        "risk_level": "high",
        "category": "crowdsec",
    },
    {
        "name": "delete_crowdsec_decision",
        "description": (
            "Remove a CrowdSec decision by decision ID or IP address. "
            "Use this to unban an IP that was incorrectly blocked. "
            "Get the decision ID from get_crowdsec_decisions(). "
            "Provide either 'id' or 'ip' — if IP is provided, all decisions for that IP are removed."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "id": {"type": "integer", "description": "Decision ID to remove"},
                "ip": {
                    "type": "string",
                    "description": "IP address to unban (removes all decisions)",
                },
            },
        },
        "handler": delete_crowdsec_decision,
        "confirm": True,
        "confirm_message": "Remove CrowdSec decision (unban)?",
        "risk_level": "high",
        "category": "crowdsec",
    },
]
