"""Target categorization engine — classify paths, URLs, and commands on-device.

Privacy-first: converts raw targets into category labels before telemetry.
Server receives "credential_ssh" not "/home/user/.ssh/id_rsa".

Zero external dependencies. This module runs on the user's machine.
"""
import re
from urllib.parse import urlparse


# --- Path Categories ---
# Each tuple: (regex_pattern, category, sensitivity)
# sensitivity: "critical" | "high" | "medium" | "low" | "none"
PATH_CATEGORIES = [
    # SSH credentials
    (r'\.ssh/(id_|authorized_keys|known_hosts|config)', "credential_ssh", "critical"),
    (r'\.ssh/', "ssh_config", "high"),

    # Cloud credentials
    (r'\.aws/(credentials|config)', "credential_cloud", "critical"),
    (r'\.azure/', "credential_cloud", "high"),
    (r'\.gcloud/', "credential_cloud", "high"),
    (r'\.config/gcloud/', "credential_cloud", "high"),
    (r'\.kube/(config|kubeconfig)', "credential_cloud", "critical"),

    # API keys and tokens
    (r'\.env($|[._-](?!example|template|sample|tpl))', "env_file", "critical"),
    (r'\.npmrc', "credential_registry", "high"),
    (r'\.pypirc', "credential_registry", "high"),
    (r'\.docker/config\.json', "credential_registry", "high"),
    (r'\.netrc', "credential_generic", "critical"),

    # Vault/secret management
    (r'\.vault-token', "credential_vault", "critical"),
    (r'\.vault/', "credential_vault", "high"),

    # GPG/PGP keys
    (r'\.gnupg/', "credential_gpg", "critical"),

    # Database files
    (r'\.(sqlite3?|db|mdb)$', "database_file", "high"),
    (r'\.(sql|dmp|dump)(\.gz)?$', "database_dump", "high"),
    (r'\.pgpass', "credential_database", "critical"),
    (r'\.my\.cnf', "credential_database", "critical"),

    # Certificate and key files (including .bak/.old backups)
    (r'\.(pem|key|crt|cert|p12|pfx|jks)([._-](bak|old|backup|orig))?$', "certificate_file", "critical"),

    # Password stores
    (r'\.password-store/', "password_store", "critical"),
    (r'pass(word)?\.txt', "password_file", "critical"),
    (r'credentials\.(json|yaml|yml|xml|ini|cfg)$', "credential_file", "critical"),
    (r'secrets?\.(json|yaml|yml|xml|ini|cfg)$', "credential_file", "critical"),

    # Browser data
    (r'\.mozilla/firefox/.*\.(sqlite|json)', "browser_data", "high"),
    (r'\.config/google-chrome/', "browser_data", "high"),
    (r'\.config/chromium/', "browser_data", "high"),

    # Shell history (may contain typed passwords)
    (r'\.(bash_history|zsh_history|fish_history)', "shell_history", "high"),

    # System config
    (r'/etc/shadow', "system_credential", "critical"),
    (r'/etc/passwd', "system_config", "medium"),
    (r'/etc/sudoers', "system_config", "high"),
    (r'/etc/hosts', "system_config", "low"),

    # Git config (may contain tokens)
    (r'\.gitconfig', "git_config", "medium"),
    (r'\.git-credentials', "credential_git", "critical"),
    (r'\.git/config', "git_config", "medium"),

    # Obol/monitoring config (self-referential)
    (r'\.obol/', "monitoring_config", "high"),
    (r'\.claude/', "agent_config", "high"),
    (r'settings\.json', "agent_config", "medium"),

    # Infrastructure config
    (r'docker-compose\.(yml|yaml)', "infra_config", "medium"),
    (r'Dockerfile', "infra_config", "low"),
    (r'\.terraform/', "infra_config", "high"),
    (r'ansible/', "infra_config", "medium"),
    (r'kubernetes/', "infra_config", "medium"),
    (r'k8s/', "infra_config", "medium"),

    # Test files (before source code so test .py files match here)
    (r'tests?[_/]', "test_file", "none"),
    (r'spec[_/]', "test_file", "none"),
    (r'__tests__/', "test_file", "none"),

    # Package/dependency files (before source code for lock files)
    (r'(requirements|Pipfile|Gemfile|Cargo\.toml|go\.mod)', "dependency_manifest", "low"),
    (r'(node_modules|\.venv|venv|\.tox)/', "dependency_dir", "none"),
    (r'(\.lock|lock\.json|\.sum)$', "lock_file", "none"),
    (r'package\.json$', "dependency_manifest", "low"),

    # Source code (low sensitivity)
    (r'\.(py|js|ts|go|rs|java|c|cpp|rb|php|swift|kt)$', "source_code", "none"),
    (r'\.(json|yaml|yml|toml|ini|cfg|xml)$', "config_file", "low"),
    (r'\.(md|txt|rst|org)$', "documentation", "none"),
    (r'\.(html|css|scss|less)$', "web_asset", "none"),
    (r'\.(jpg|jpeg|png|gif|svg|ico|webp)$', "image_file", "none"),
    (r'\.(log|out)$', "log_file", "low"),
]


# --- URL Categories ---
# Known-safe domains grouped by purpose
KNOWN_GOOD_DOMAINS = {
    # Package registries
    "pypi.org": "package_registry",
    "files.pythonhosted.org": "package_registry",
    "registry.npmjs.org": "package_registry",
    "crates.io": "package_registry",
    "rubygems.org": "package_registry",
    "pkg.go.dev": "package_registry",
    "maven.org": "package_registry",
    "download.pytorch.org": "package_registry",
    "nuget.org": "package_registry",

    # Documentation
    "docs.python.org": "documentation",
    "developer.mozilla.org": "documentation",
    "docs.rs": "documentation",
    "go.dev": "documentation",
    "docs.docker.com": "documentation",
    "docs.github.com": "documentation",
    "docs.anthropic.com": "documentation",
    "platform.openai.com": "documentation",

    # Code hosting
    "github.com": "code_hosting",
    "gitlab.com": "code_hosting",
    "bitbucket.org": "code_hosting",
    "raw.githubusercontent.com": "code_hosting",
    "gist.github.com": "code_hosting",

    # Search engines
    "google.com": "search_engine",
    "duckduckgo.com": "search_engine",
    "bing.com": "search_engine",

    # Stack Overflow / forums
    "stackoverflow.com": "developer_forum",
    "stackexchange.com": "developer_forum",
    "reddit.com": "social_media",

    # Cloud provider APIs
    "amazonaws.com": "cloud_provider",
    "azure.microsoft.com": "cloud_provider",
    "cloud.google.com": "cloud_provider",

    # CDN / assets
    "cdn.jsdelivr.net": "cdn",
    "cdnjs.cloudflare.com": "cdn",
    "unpkg.com": "cdn",

    # Monitoring/reporting (self)
    "obol.grayowlresearch.com": "self_api",
    "api.grayowlresearch.com": "self_api",
}

# Known exfiltration-risk domains (pastes, file shares, tunnels)
KNOWN_RISKY_DOMAINS = {
    "pastebin.com": "paste_service",
    "paste.ee": "paste_service",
    "hastebin.com": "paste_service",
    "dpaste.org": "paste_service",
    "ghostbin.com": "paste_service",
    "rentry.co": "paste_service",
    "0x0.st": "file_share",
    "transfer.sh": "file_share",
    "file.io": "file_share",
    "gofile.io": "file_share",
    "temp.sh": "file_share",
    "ngrok.io": "tunnel_service",
    "ngrok.app": "tunnel_service",
    "trycloudflare.com": "tunnel_service",
    "loca.lt": "tunnel_service",
    "serveo.net": "tunnel_service",
    "webhook.site": "webhook_tester",
    "requestbin.com": "webhook_tester",
    "pipedream.net": "webhook_tester",
    "beeceptor.com": "webhook_tester",
    "requestcatcher.com": "webhook_tester",
    "hookbin.com": "webhook_tester",
    "burpcollaborator.net": "security_tool",
    "interact.sh": "security_tool",
    "oast.fun": "security_tool",
    "canarytokens.com": "security_tool",
    "dnslog.cn": "security_tool",
    # IPFS gateways (can be used for data exfiltration/hosting C2)
    "gateway.pinata.cloud": "ipfs_gateway",
    "dweb.link": "ipfs_gateway",
    "ipfs.io": "ipfs_gateway",
    "cloudflare-ipfs.com": "ipfs_gateway",
    "w3s.link": "ipfs_gateway",
    # Additional tunnel services
    "bore.digital": "tunnel_service",
    "localhost.run": "tunnel_service",
    "telebit.cloud": "tunnel_service",
    "playit.gg": "tunnel_service",
    # Additional file share / exfil services
    "catbox.moe": "file_share",
    "litterbox.catbox.moe": "file_share",
    "uploadfiles.io": "file_share",
    "fileditch.com": "file_share",
}

# Internal/metadata endpoints (SSRF targets)
INTERNAL_PATTERNS = [
    (r'^https?://169\.254\.169\.254', "cloud_metadata"),
    (r'^https?://metadata\.google\.internal', "cloud_metadata"),
    (r'^https?://100\.100\.100\.200', "cloud_metadata"),
    (r'^https?://127\.0\.0\.', "localhost"),
    (r'^https?://localhost', "localhost"),
    (r'^https?://0\.0\.0\.0', "localhost"),
    (r'^https?://\[::1\]', "localhost"),
    (r'^https?://\[::ffff:127\.', "localhost"),
    (r'^https?://\[::ffff:(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)', "private_network"),
    (r'^https?://\[fe80:', "private_network"),
    (r'^https?://10\.', "private_network"),
    (r'^https?://172\.(1[6-9]|2[0-9]|3[01])\.', "private_network"),
    (r'^https?://192\.168\.', "private_network"),
]


def categorize_path(path: str) -> dict:
    """Categorize a file path into a privacy-safe label.

    Returns:
        {"category": str, "sensitivity": str}
    """
    if not path or not isinstance(path, str):
        return {"category": "unknown", "sensitivity": "low"}

    # Normalize path separators
    normalized = path.replace("\\", "/").lower()

    # Strip home directory prefix for pattern matching
    # /home/user/.ssh/id_rsa → .ssh/id_rsa
    home_stripped = re.sub(r'^(/home/[^/]+|/Users/[^/]+|~)', '', normalized)

    for pattern, category, sensitivity in PATH_CATEGORIES:
        if re.search(pattern, home_stripped, re.IGNORECASE):
            return {"category": category, "sensitivity": sensitivity}

    # Fallback: classify by directory depth and extension
    if "/" in normalized:
        ext = normalized.rsplit(".", 1)[-1] if "." in normalized.split("/")[-1] else ""
        if ext in ("py", "js", "ts", "go", "rs", "java", "c", "cpp", "rb"):
            return {"category": "source_code", "sensitivity": "none"}

    return {"category": "general_file", "sensitivity": "none"}


def categorize_url(url: str, api_url: str = None,
                   allowed_destinations: list = None) -> dict:
    """Categorize a URL into a privacy-safe label.

    Returns:
        {"category": str, "risk": str, "domain_hash": str}

    risk levels: "known_good", "user_allowed", "self_api", "known_risky", "unknown"
    """
    if not url or not isinstance(url, str):
        return {"category": "unknown", "risk": "unknown", "domain_hash": ""}

    try:
        import hashlib

        # Block dangerous URI schemes
        url_lower = url.lower().strip()
        if url_lower.startswith(("javascript:", "data:", "vbscript:")):
            return {"category": "dangerous_scheme", "risk": "known_risky", "domain_hash": ""}

        parsed = urlparse(url)
        domain = (parsed.hostname or "").lower()
        if not domain:
            return {"category": "unknown", "risk": "unknown", "domain_hash": ""}

        # Validate port if present
        port = parsed.port
        if port is not None and (port < 1 or port > 65535):
            return {"category": "invalid_port", "risk": "unknown",
                    "domain_hash": hashlib.sha256(domain.encode()).hexdigest()[:16]}

        domain_hash = hashlib.sha256(domain.encode()).hexdigest()[:16]

        # Check internal/metadata (SSRF)
        for pattern, cat in INTERNAL_PATTERNS:
            if re.match(pattern, url, re.IGNORECASE):
                return {
                    "category": cat,
                    "risk": "ssrf_target",
                    "domain_hash": domain_hash,
                }

        # Check self-API
        if api_url:
            try:
                api_domain = urlparse(api_url).hostname
                if api_domain and domain == api_domain.lower():
                    return {
                        "category": "self_api",
                        "risk": "self_api",
                        "domain_hash": domain_hash,
                    }
            except Exception:
                pass

        # Check user allowlist
        if allowed_destinations:
            for allowed in allowed_destinations:
                allowed_lower = allowed.lower()
                if allowed_lower.startswith("*."):
                    suffix = allowed_lower[1:]  # ".example.com"
                    if domain.endswith(suffix) or domain == allowed_lower[2:]:
                        return {
                            "category": "user_allowed",
                            "risk": "user_allowed",
                            "domain_hash": domain_hash,
                        }
                elif domain == allowed_lower:
                    return {
                        "category": "user_allowed",
                        "risk": "user_allowed",
                        "domain_hash": domain_hash,
                    }

        # Check known good (exact + subdomain match)
        for known_domain, cat in KNOWN_GOOD_DOMAINS.items():
            if domain == known_domain or domain.endswith("." + known_domain):
                return {
                    "category": cat,
                    "risk": "known_good",
                    "domain_hash": domain_hash,
                }

        # Check known risky
        for risky_domain, cat in KNOWN_RISKY_DOMAINS.items():
            if domain == risky_domain or domain.endswith("." + risky_domain):
                return {
                    "category": cat,
                    "risk": "known_risky",
                    "domain_hash": domain_hash,
                }

        # Tor hidden services (.onion TLD) — common C2/exfil channel
        if domain.endswith(".onion"):
            return {
                "category": "tor_hidden_service",
                "risk": "known_risky",
                "domain_hash": domain_hash,
            }

        # URL path pattern classification
        path = (parsed.path or "").lower()
        url_pattern = "api_call"
        if any(p in path for p in ["/upload", "/file", "/blob", "/attachment"]):
            url_pattern = "file_upload"
        elif any(p in path for p in ["/webhook", "/hook", "/callback"]):
            url_pattern = "webhook"
        elif any(p in path for p in ["/auth", "/login", "/oauth", "/token"]):
            url_pattern = "auth_endpoint"

        return {
            "category": url_pattern,
            "risk": "unknown",
            "domain_hash": domain_hash,
        }

    except Exception:
        return {"category": "unknown", "risk": "unknown", "domain_hash": ""}


def categorize_command(command: str) -> dict:
    """Categorize a Bash command into a privacy-safe label.

    Returns:
        {"category": str, "risk": str}
    """
    if not command or not isinstance(command, str):
        return {"category": "unknown", "risk": "none"}

    cmd = command.strip().lower()

    # Package management
    if re.search(r'\b(pip|pip3|npm|yarn|pnpm|cargo|gem|composer|go\s+get)\b', cmd):
        return {"category": "package_install", "risk": "low"}

    # Git operations
    if re.search(r'\bgit\b', cmd):
        if re.search(r'\bgit\s+(push|remote|fetch|pull)\b', cmd):
            return {"category": "git_network", "risk": "low"}
        return {"category": "git_local", "risk": "none"}

    # Build/test tools
    if re.search(r'\b(make|cmake|cargo\s+build|go\s+build|gradle|mvn|pytest|jest|mocha)\b', cmd):
        return {"category": "build_test", "risk": "none"}

    # Docker/container operations
    if re.search(r'\b(docker|podman|kubectl)\b', cmd):
        return {"category": "container_ops", "risk": "low"}

    # System admin (elevated)
    if re.search(r'\b(sudo|su\s|systemctl|service\s|journalctl)\b', cmd):
        return {"category": "system_admin", "risk": "medium"}

    # Network tools
    if re.search(r'\b(curl|wget|httpie|http\s)\b', cmd):
        return {"category": "network_request", "risk": "medium"}
    if re.search(r'\b(ssh|scp|rsync|sftp)\b', cmd):
        return {"category": "remote_access", "risk": "medium"}

    # File operations
    if re.search(r'\b(rm|rmdir|shred|srm)\b', cmd):
        return {"category": "file_delete", "risk": "medium"}
    if re.search(r'\b(cp|mv|ln)\b', cmd):
        return {"category": "file_move", "risk": "low"}
    if re.search(r'\b(cat|head|tail|less|more|bat)\b', cmd):
        return {"category": "file_read", "risk": "none"}

    # Process management
    if re.search(r'\b(kill|pkill|killall|ps|top|htop)\b', cmd):
        return {"category": "process_mgmt", "risk": "low"}

    # Scripting/execution
    if re.search(r'\b(python3?|node|ruby|perl|bash|sh|zsh)\s', cmd):
        return {"category": "script_exec", "risk": "low"}

    # Search/find
    if re.search(r'\b(find|locate|grep|rg|ag|fd)\b', cmd):
        return {"category": "search", "risk": "none"}

    # List/info
    if re.search(r'\b(ls|pwd|whoami|id|uname|hostname|date|df|du|free)\b', cmd):
        return {"category": "system_info", "risk": "none"}

    return {"category": "shell_command", "risk": "none"}


def categorize_target(tool_name: str, target: str,
                      api_url: str = None,
                      allowed_destinations: list = None) -> dict:
    """High-level categorizer: dispatches to path/url/command categorizers.

    Returns unified dict:
        {"category": str, "sensitivity": str, "risk": str}
    """
    if not target or not isinstance(target, str):
        return {"category": "unknown", "sensitivity": "none", "risk": "none"}

    if tool_name in ("Read", "Write", "Edit"):
        result = categorize_path(target)
        return {
            "category": result["category"],
            "sensitivity": result["sensitivity"],
            "risk": "none",
        }

    if tool_name in ("WebFetch", "WebSearch"):
        result = categorize_url(
            target, api_url=api_url,
            allowed_destinations=allowed_destinations,
        )
        return {
            "category": result["category"],
            "sensitivity": "none",
            "risk": result["risk"],
        }

    if tool_name == "Bash":
        result = categorize_command(target)
        return {
            "category": result["category"],
            "sensitivity": "none",
            "risk": result["risk"],
        }

    if tool_name == "Glob":
        return {"category": "file_search", "sensitivity": "none", "risk": "none"}

    if tool_name == "Grep":
        return {"category": "content_search", "sensitivity": "none", "risk": "none"}

    if tool_name == "Task":
        return {"category": "agent_task", "sensitivity": "none", "risk": "none"}

    if tool_name.startswith("mcp__"):
        return {"category": "mcp_tool", "sensitivity": "low", "risk": "low"}

    return {"category": "other_tool", "sensitivity": "none", "risk": "none"}
