"""
OSINT Blocklist - Dangerous site protection for dark web reconnaissance

Provides comprehensive blocklisting of known malicious domains including:
- Ransomware infrastructure (LockBit, BlackCat/ALPHV, Conti, REvil)
- Zero-click exploit delivery (Pegasus/NSO Group patterns)
- Malware distribution and C2 servers
- Phishing and credential harvesting
- Exploit kits and drive-by download sites
- Known threat actor infrastructure

Sources:
- CISA Known Exploited Vulnerabilities (KEV)
- FBI Flash Reports on ransomware
- Citizen Lab research on spyware
- Amnesty International Pegasus infrastructure
- Community threat intelligence feeds

WARNING: This blocklist is for DEFENSIVE purposes only.
These domains should NEVER be visited, even for research.
"""

import hashlib
import json
import re
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Optional
from urllib.parse import urlparse


class ThreatCategory(Enum):
    """Categories of dangerous sites."""

    # Malware & Ransomware
    RANSOMWARE_INFRASTRUCTURE = "ransomware_infrastructure"
    MALWARE_DISTRIBUTION = "malware_distribution"
    COMMAND_AND_CONTROL = "command_and_control"
    EXPLOIT_KIT = "exploit_kit"
    CRYPTOMINER = "cryptominer"
    BOTNET = "botnet"

    # Spyware & Surveillance
    ZERO_CLICK_EXPLOIT = "zero_click_exploit"
    SPYWARE_DELIVERY = "spyware_delivery"
    NSO_PEGASUS = "nso_pegasus"
    SURVEILLANCE_VENDOR = "surveillance_vendor"

    # Fraud & Phishing
    PHISHING = "phishing"
    CREDENTIAL_HARVESTING = "credential_harvesting"
    SCAM = "scam"
    FRAUD = "fraud"

    # Illegal Content (DO NOT ACCESS)
    ILLEGAL_CONTENT = "illegal_content"
    CSAM = "csam"  # Child exploitation - NEVER access
    WEAPONS_TRAFFICKING = "weapons_trafficking"
    HUMAN_TRAFFICKING = "human_trafficking"

    # High-Risk Infrastructure
    BULLETPROOF_HOSTING = "bulletproof_hosting"
    DARK_MARKET = "dark_market"
    CARDING = "carding"

    # DGA & Dynamic
    DGA_DOMAIN = "dga_domain"
    FAST_FLUX = "fast_flux"


class ThreatSeverity(Enum):
    """Severity levels for blocked threats."""

    CRITICAL = "critical"  # Zero-click, CSAM, ransomware C2
    HIGH = "high"  # Malware distribution, exploit kits
    MEDIUM = "medium"  # Phishing, scams
    LOW = "low"  # Suspicious, newly registered


@dataclass
class BlockedDomain:
    """A blocked domain entry."""

    domain: str
    category: ThreatCategory
    severity: ThreatSeverity
    description: str
    source: str = "internal"
    added_date: datetime = field(default_factory=datetime.utcnow)
    threat_actor: Optional[str] = None
    tags: list[str] = field(default_factory=list)
    ioc_hash: Optional[str] = None

    def matches(self, url: str) -> bool:
        """Check if URL matches this blocked domain."""
        try:
            parsed = urlparse(url)
            host = parsed.netloc or parsed.path
            host = host.lower().strip()

            # Exact match
            if host == self.domain:
                return True

            # Subdomain match
            if host.endswith("." + self.domain):
                return True

            # Pattern match for regex domains
            if self.domain.startswith("regex:"):
                pattern = self.domain[6:]
                if re.match(pattern, host):
                    return True

            return False
        except Exception:
            return False

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "domain": self.domain,
            "category": self.category.value,
            "severity": self.severity.value,
            "description": self.description,
            "source": self.source,
            "added_date": self.added_date.isoformat(),
            "threat_actor": self.threat_actor,
            "tags": self.tags,
        }


# =============================================================================
# KNOWN RANSOMWARE INFRASTRUCTURE
# Sources: CISA, FBI Flash Reports, Unit42, Trend Micro
# =============================================================================

RANSOMWARE_DOMAINS = [
    # LockBit Infrastructure (Operation Cronos seized many, but patterns remain)
    BlockedDomain(
        domain="regex:lockbit[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="LockBit ransomware leak site pattern",
        source="CISA AA23-165A",
        threat_actor="LockBit",
        tags=["ransomware", "data_leak", "extortion"],
    ),
    BlockedDomain(
        domain="regex:lockbit.*decryptor.*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="LockBit decryptor/payment portal pattern",
        source="FBI Flash",
        threat_actor="LockBit",
        tags=["ransomware", "payment"],
    ),
    # BlackCat/ALPHV Infrastructure
    BlockedDomain(
        domain="regex:alphv[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="ALPHV/BlackCat ransomware infrastructure",
        source="CISA AA23-353A",
        threat_actor="BlackCat/ALPHV",
        tags=["ransomware", "RaaS"],
    ),
    BlockedDomain(
        domain="regex:blackcat[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="BlackCat ransomware leak site",
        source="FBI IOC Release",
        threat_actor="BlackCat/ALPHV",
        tags=["ransomware", "data_leak"],
    ),
    # Conti (defunct but patterns may resurface)
    BlockedDomain(
        domain="regex:conti[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Conti ransomware infrastructure pattern",
        source="CISA",
        threat_actor="Conti",
        tags=["ransomware", "defunct"],
    ),
    # REvil/Sodinokibi
    BlockedDomain(
        domain="regex:revil[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="REvil ransomware infrastructure",
        source="CISA",
        threat_actor="REvil",
        tags=["ransomware"],
    ),
    BlockedDomain(
        domain="regex:sodinokibi[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Sodinokibi ransomware infrastructure",
        source="CISA",
        threat_actor="REvil",
        tags=["ransomware"],
    ),
    # Hive (FBI seized but patterns tracked)
    BlockedDomain(
        domain="regex:hive[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Hive ransomware infrastructure",
        source="FBI",
        threat_actor="Hive",
        tags=["ransomware", "seized"],
    ),
    # Clop/Cl0p
    BlockedDomain(
        domain="regex:cl0p[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Clop ransomware infrastructure",
        source="CISA",
        threat_actor="Clop",
        tags=["ransomware", "MOVEit"],
    ),
    # Play ransomware
    BlockedDomain(
        domain="regex:play[a-z0-9]*news.*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Play ransomware leak site",
        source="Trend Micro",
        threat_actor="Play",
        tags=["ransomware"],
    ),
    # Royal ransomware
    BlockedDomain(
        domain="regex:royal[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Royal ransomware infrastructure",
        source="CISA",
        threat_actor="Royal",
        tags=["ransomware"],
    ),
    # Akira ransomware
    BlockedDomain(
        domain="regex:akira[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Akira ransomware infrastructure",
        source="CISA",
        threat_actor="Akira",
        tags=["ransomware"],
    ),
    # Medusa ransomware
    BlockedDomain(
        domain="regex:medusa[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="Medusa ransomware leak site",
        source="Unit42",
        threat_actor="Medusa",
        tags=["ransomware"],
    ),
    # BianLian
    BlockedDomain(
        domain="regex:bianlian[a-z0-9]*\\.onion",
        category=ThreatCategory.RANSOMWARE_INFRASTRUCTURE,
        severity=ThreatSeverity.CRITICAL,
        description="BianLian ransomware infrastructure",
        source="CISA",
        threat_actor="BianLian",
        tags=["ransomware"],
    ),
]

# =============================================================================
# ZERO-CLICK EXPLOIT & SPYWARE INFRASTRUCTURE
# Sources: Citizen Lab, Amnesty International, Google Project Zero
# =============================================================================

SPYWARE_DOMAINS = [
    # NSO Group Pegasus patterns (V3 infrastructure patterns)
    # Based on Amnesty International's Pegasus infrastructure research
    BlockedDomain(
        domain="regex:.*\\.info\\d+\\.info",
        category=ThreatCategory.NSO_PEGASUS,
        severity=ThreatSeverity.CRITICAL,
        description="Pegasus V3 infrastructure pattern",
        source="Amnesty International",
        threat_actor="NSO Group",
        tags=["zero-click", "spyware", "pegasus"],
    ),
    BlockedDomain(
        domain="regex:.*-ede\\..*\\.com",
        category=ThreatCategory.NSO_PEGASUS,
        severity=ThreatSeverity.CRITICAL,
        description="Pegasus delivery infrastructure pattern",
        source="Citizen Lab",
        threat_actor="NSO Group",
        tags=["zero-click", "spyware"],
    ),
    # Pegasus Anonymizing Transmission Network patterns
    BlockedDomain(
        domain="regex:.*cdn.*static.*\\.(com|net|org)",
        category=ThreatCategory.SPYWARE_DELIVERY,
        severity=ThreatSeverity.HIGH,
        description="Suspected spyware CDN pattern - verify before blocking",
        source="Citizen Lab Research",
        threat_actor="Various",
        tags=["spyware", "needs_verification"],
    ),
    # Predator spyware (Cytrox/Intellexa)
    BlockedDomain(
        domain="regex:.*intellexa.*",
        category=ThreatCategory.SURVEILLANCE_VENDOR,
        severity=ThreatSeverity.CRITICAL,
        description="Intellexa surveillance infrastructure",
        source="Citizen Lab",
        threat_actor="Intellexa",
        tags=["spyware", "predator"],
    ),
    # Candiru spyware
    BlockedDomain(
        domain="regex:.*candiru.*",
        category=ThreatCategory.SURVEILLANCE_VENDOR,
        severity=ThreatSeverity.CRITICAL,
        description="Candiru spyware infrastructure",
        source="Citizen Lab",
        threat_actor="Candiru",
        tags=["spyware"],
    ),
    # QuaDream spyware
    BlockedDomain(
        domain="regex:.*quadream.*",
        category=ThreatCategory.SURVEILLANCE_VENDOR,
        severity=ThreatSeverity.CRITICAL,
        description="QuaDream spyware infrastructure",
        source="Citizen Lab",
        threat_actor="QuaDream",
        tags=["spyware", "reign"],
    ),
]

# =============================================================================
# MALWARE DISTRIBUTION & C2
# Sources: CISA advisories, malware analysis reports
# =============================================================================

MALWARE_C2_DOMAINS = [
    # Emotet patterns
    BlockedDomain(
        domain="regex:.*emotet.*",
        category=ThreatCategory.MALWARE_DISTRIBUTION,
        severity=ThreatSeverity.HIGH,
        description="Emotet malware infrastructure",
        source="CISA",
        threat_actor="Emotet",
        tags=["malware", "loader"],
    ),
    # QakBot/Qbot patterns
    BlockedDomain(
        domain="regex:.*qakbot.*",
        category=ThreatCategory.MALWARE_DISTRIBUTION,
        severity=ThreatSeverity.HIGH,
        description="QakBot malware infrastructure",
        source="CISA",
        threat_actor="QakBot",
        tags=["malware", "loader"],
    ),
    # TrickBot patterns
    BlockedDomain(
        domain="regex:.*trickbot.*",
        category=ThreatCategory.MALWARE_DISTRIBUTION,
        severity=ThreatSeverity.HIGH,
        description="TrickBot malware infrastructure",
        source="CISA",
        threat_actor="TrickBot",
        tags=["malware", "banking"],
    ),
    # IcedID patterns
    BlockedDomain(
        domain="regex:.*icedid.*",
        category=ThreatCategory.MALWARE_DISTRIBUTION,
        severity=ThreatSeverity.HIGH,
        description="IcedID malware infrastructure",
        source="CISA",
        threat_actor="IcedID",
        tags=["malware", "loader"],
    ),
    # LummaC2 Stealer (Recent CISA advisory AA25-141B)
    BlockedDomain(
        domain="regex:.*lummac2.*",
        category=ThreatCategory.COMMAND_AND_CONTROL,
        severity=ThreatSeverity.HIGH,
        description="LummaC2 stealer C2 infrastructure",
        source="CISA AA25-141B",
        threat_actor="LummaC2",
        tags=["infostealer", "c2"],
    ),
    # Raccoon Stealer
    BlockedDomain(
        domain="regex:.*raccoon.*stealer.*",
        category=ThreatCategory.MALWARE_DISTRIBUTION,
        severity=ThreatSeverity.HIGH,
        description="Raccoon Stealer infrastructure",
        source="FBI",
        threat_actor="Raccoon",
        tags=["infostealer"],
    ),
    # RedLine Stealer
    BlockedDomain(
        domain="regex:.*redline.*",
        category=ThreatCategory.MALWARE_DISTRIBUTION,
        severity=ThreatSeverity.HIGH,
        description="RedLine Stealer infrastructure",
        source="Threat Intel",
        threat_actor="RedLine",
        tags=["infostealer"],
    ),
    # Cobalt Strike beacons (common C2)
    BlockedDomain(
        domain="regex:.*cobaltstrike.*",
        category=ThreatCategory.COMMAND_AND_CONTROL,
        severity=ThreatSeverity.HIGH,
        description="Cobalt Strike C2 indicator",
        source="Threat Intel",
        threat_actor="Various",
        tags=["c2", "pentest_tool_abuse"],
    ),
]

# =============================================================================
# EXPLOIT KITS & DRIVE-BY DOWNLOADS
# =============================================================================

EXPLOIT_KIT_DOMAINS = [
    BlockedDomain(
        domain="regex:.*angler.*kit.*",
        category=ThreatCategory.EXPLOIT_KIT,
        severity=ThreatSeverity.HIGH,
        description="Angler Exploit Kit pattern",
        source="Security Research",
        tags=["exploit_kit"],
    ),
    BlockedDomain(
        domain="regex:.*rig.*exploit.*",
        category=ThreatCategory.EXPLOIT_KIT,
        severity=ThreatSeverity.HIGH,
        description="RIG Exploit Kit pattern",
        source="Security Research",
        tags=["exploit_kit"],
    ),
    BlockedDomain(
        domain="regex:.*magnitude.*",
        category=ThreatCategory.EXPLOIT_KIT,
        severity=ThreatSeverity.HIGH,
        description="Magnitude Exploit Kit pattern",
        source="Security Research",
        tags=["exploit_kit"],
    ),
]

# =============================================================================
# ILLEGAL CONTENT - ABSOLUTE BLOCKLIST
# These should NEVER be accessed under ANY circumstances
# =============================================================================

ILLEGAL_CONTENT_PATTERNS = [
    # CSAM indicators - NEVER access, report to authorities
    BlockedDomain(
        domain="regex:.*\\b(cp|pedo|child.*porn|loli|preteen).*\\.onion",
        category=ThreatCategory.CSAM,
        severity=ThreatSeverity.CRITICAL,
        description="CSAM indicator - DO NOT ACCESS - Report to NCMEC/authorities",
        source="Internal Policy",
        tags=["illegal", "csam", "report_required"],
    ),
    # Weapons trafficking
    BlockedDomain(
        domain="regex:.*(weapons|arms|guns).*market.*\\.onion",
        category=ThreatCategory.WEAPONS_TRAFFICKING,
        severity=ThreatSeverity.CRITICAL,
        description="Suspected weapons trafficking site",
        source="Internal Policy",
        tags=["illegal", "weapons"],
    ),
    # Human trafficking
    BlockedDomain(
        domain="regex:.*(human|slave|traffic).*market.*\\.onion",
        category=ThreatCategory.HUMAN_TRAFFICKING,
        severity=ThreatSeverity.CRITICAL,
        description="Suspected human trafficking site",
        source="Internal Policy",
        tags=["illegal", "trafficking"],
    ),
    # Hitman/murder-for-hire scams (usually scams but still illegal to solicit)
    BlockedDomain(
        domain="regex:.*(hitman|assassin|murder.*hire).*\\.onion",
        category=ThreatCategory.ILLEGAL_CONTENT,
        severity=ThreatSeverity.CRITICAL,
        description="Murder-for-hire site (usually scam, still illegal)",
        source="Internal Policy",
        tags=["illegal", "violence"],
    ),
]

# =============================================================================
# DARK WEB MARKETS & CARDING
# =============================================================================

DARK_MARKET_DOMAINS = [
    # Generic market patterns
    BlockedDomain(
        domain="regex:.*(market|shop|store).*\\.(onion)",
        category=ThreatCategory.DARK_MARKET,
        severity=ThreatSeverity.MEDIUM,
        description="Potential dark web marketplace",
        source="OSINT Research",
        tags=["market", "needs_review"],
    ),
    # Carding forums
    BlockedDomain(
        domain="regex:.*(card|cvv|fullz|dump).*\\.(onion|su|ru)",
        category=ThreatCategory.CARDING,
        severity=ThreatSeverity.HIGH,
        description="Carding/financial fraud forum",
        source="OSINT Research",
        tags=["fraud", "carding"],
    ),
]

# =============================================================================
# DGA DETECTION PATTERNS
# Domain Generation Algorithm patterns used by malware
# =============================================================================

DGA_PATTERNS = [
    # High entropy domains (random-looking)
    BlockedDomain(
        domain="regex:^[a-z]{15,}\\.(com|net|org|info|biz)$",
        category=ThreatCategory.DGA_DOMAIN,
        severity=ThreatSeverity.MEDIUM,
        description="Potential DGA domain (high entropy)",
        source="Heuristic",
        tags=["dga", "heuristic"],
    ),
    # Numeric heavy domains
    BlockedDomain(
        domain="regex:^[a-z0-9]{20,}\\.(com|net|org)$",
        category=ThreatCategory.DGA_DOMAIN,
        severity=ThreatSeverity.MEDIUM,
        description="Potential DGA domain (long alphanumeric)",
        source="Heuristic",
        tags=["dga", "heuristic"],
    ),
]


class OSINTBlocklist:
    """
    Comprehensive blocklist for OSINT operations.

    Protects researchers from accidentally accessing:
    - Ransomware infrastructure
    - Zero-click exploit delivery sites
    - Malware distribution and C2 servers
    - Illegal content
    - Known threat actor infrastructure

    Usage:
        blocklist = OSINTBlocklist()

        # Check if URL is safe
        if blocklist.is_blocked(url):
            result = blocklist.check_url(url)
            print(f"BLOCKED: {result.category} - {result.description}")

        # Filter search results
        safe_results = blocklist.filter_results(search_results)
    """

    def __init__(
        self,
        enable_ransomware: bool = True,
        enable_spyware: bool = True,
        enable_malware: bool = True,
        enable_exploit_kits: bool = True,
        enable_illegal: bool = True,
        enable_markets: bool = False,  # Optional - may be research targets
        enable_dga: bool = True,
        custom_blocklist_path: Optional[Path] = None,
    ):
        """
        Initialize blocklist with selected categories.

        Args:
            enable_ransomware: Block ransomware infrastructure
            enable_spyware: Block spyware/zero-click infrastructure
            enable_malware: Block malware distribution & C2
            enable_exploit_kits: Block exploit kit domains
            enable_illegal: Block illegal content (CSAM, trafficking)
            enable_markets: Block dark markets (optional for research)
            enable_dga: Block suspected DGA domains
            custom_blocklist_path: Path to custom blocklist JSON
        """
        self._domains: list[BlockedDomain] = []

        # Load enabled categories
        if enable_ransomware:
            self._domains.extend(RANSOMWARE_DOMAINS)

        if enable_spyware:
            self._domains.extend(SPYWARE_DOMAINS)

        if enable_malware:
            self._domains.extend(MALWARE_C2_DOMAINS)

        if enable_exploit_kits:
            self._domains.extend(EXPLOIT_KIT_DOMAINS)

        if enable_illegal:
            self._domains.extend(ILLEGAL_CONTENT_PATTERNS)

        if enable_markets:
            self._domains.extend(DARK_MARKET_DOMAINS)

        if enable_dga:
            self._domains.extend(DGA_PATTERNS)

        # Load custom blocklist
        if custom_blocklist_path and custom_blocklist_path.exists():
            self._load_custom_blocklist(custom_blocklist_path)

        # Compile regex patterns for performance
        self._compiled_patterns: list[tuple[re.Pattern, BlockedDomain]] = []
        for domain in self._domains:
            if domain.domain.startswith("regex:"):
                pattern = domain.domain[6:]
                try:
                    compiled = re.compile(pattern, re.IGNORECASE)
                    self._compiled_patterns.append((compiled, domain))
                except re.error:
                    pass  # Invalid regex, skip

    def _load_custom_blocklist(self, path: Path) -> None:
        """Load custom blocklist from JSON file."""
        try:
            with open(path) as f:
                data = json.load(f)

            for entry in data.get("domains", []):
                self._domains.append(
                    BlockedDomain(
                        domain=entry["domain"],
                        category=ThreatCategory(entry.get("category", "malware_distribution")),
                        severity=ThreatSeverity(entry.get("severity", "medium")),
                        description=entry.get("description", "Custom blocklist entry"),
                        source=entry.get("source", "custom"),
                        threat_actor=entry.get("threat_actor"),
                        tags=entry.get("tags", []),
                    )
                )
        except Exception:
            pass  # Silently fail on invalid custom blocklist

    def is_blocked(self, url: str) -> bool:
        """
        Check if URL is blocked.

        Args:
            url: URL to check

        Returns:
            True if blocked
        """
        return self.check_url(url) is not None

    def check_url(self, url: str) -> Optional[BlockedDomain]:
        """
        Check URL against blocklist.

        Args:
            url: URL to check

        Returns:
            BlockedDomain if matched, None if safe
        """
        try:
            # Normalize URL
            url = url.lower().strip()
            if not url.startswith(("http://", "https://")):
                url = "http://" + url

            parsed = urlparse(url)
            host = parsed.netloc or parsed.path.split("/")[0]
            host = host.lower()

            # Check compiled regex patterns
            for pattern, domain in self._compiled_patterns:
                if pattern.search(host) or pattern.search(url):
                    return domain

            # Check exact matches
            for domain in self._domains:
                if not domain.domain.startswith("regex:"):
                    if domain.matches(url):
                        return domain

            return None

        except Exception:
            return None

    def filter_results(
        self,
        results: list,
        url_attr: str = "url",
        remove_blocked: bool = True,
    ) -> tuple[list, list[dict]]:
        """
        Filter search results, removing blocked URLs.

        Args:
            results: List of result objects/dicts
            url_attr: Attribute name containing URL
            remove_blocked: If True, remove blocked. If False, mark them.

        Returns:
            Tuple of (safe_results, blocked_info)
        """
        safe_results = []
        blocked_info = []

        for result in results:
            # Get URL from result
            if isinstance(result, dict):
                url = result.get(url_attr, "")
            else:
                url = getattr(result, url_attr, "")

            # Check against blocklist
            blocked = self.check_url(url)

            if blocked:
                blocked_info.append({
                    "url": url,
                    "category": blocked.category.value,
                    "severity": blocked.severity.value,
                    "description": blocked.description,
                    "threat_actor": blocked.threat_actor,
                })

                if not remove_blocked:
                    safe_results.append(result)
            else:
                safe_results.append(result)

        return safe_results, blocked_info

    def get_stats(self) -> dict:
        """Get blocklist statistics."""
        stats = {
            "total_entries": len(self._domains),
            "by_category": {},
            "by_severity": {},
        }

        for domain in self._domains:
            cat = domain.category.value
            sev = domain.severity.value

            stats["by_category"][cat] = stats["by_category"].get(cat, 0) + 1
            stats["by_severity"][sev] = stats["by_severity"].get(sev, 0) + 1

        return stats

    def add_domain(
        self,
        domain: str,
        category: ThreatCategory,
        severity: ThreatSeverity,
        description: str,
        **kwargs,
    ) -> None:
        """
        Add a domain to the blocklist.

        Args:
            domain: Domain or regex pattern (prefix with "regex:")
            category: Threat category
            severity: Threat severity
            description: Description of the threat
            **kwargs: Additional BlockedDomain fields
        """
        entry = BlockedDomain(
            domain=domain,
            category=category,
            severity=severity,
            description=description,
            **kwargs,
        )
        self._domains.append(entry)

        # Compile regex if applicable
        if domain.startswith("regex:"):
            try:
                compiled = re.compile(domain[6:], re.IGNORECASE)
                self._compiled_patterns.append((compiled, entry))
            except re.error:
                pass

    def export_blocklist(self, path: Path) -> None:
        """
        Export blocklist to JSON file.

        Args:
            path: Output file path
        """
        data = {
            "version": "1.0",
            "generated": datetime.utcnow().isoformat(),
            "total_entries": len(self._domains),
            "domains": [d.to_dict() for d in self._domains],
        }

        with open(path, "w") as f:
            json.dump(data, f, indent=2)

    def get_threat_actors(self) -> list[str]:
        """Get list of tracked threat actors."""
        actors = set()
        for domain in self._domains:
            if domain.threat_actor:
                actors.add(domain.threat_actor)
        return sorted(actors)

    def get_domains_by_actor(self, actor: str) -> list[BlockedDomain]:
        """Get all blocked domains for a threat actor."""
        return [d for d in self._domains if d.threat_actor == actor]

    def get_domains_by_category(self, category: ThreatCategory) -> list[BlockedDomain]:
        """Get all blocked domains in a category."""
        return [d for d in self._domains if d.category == category]


# =============================================================================
# THREAT INTELLIGENCE FEED INTEGRATION
# =============================================================================

class ThreatFeedIntegration:
    """
    Integration with external threat intelligence feeds.

    Supports:
    - HaGeZi DNS blocklists
    - CISA KEV indicators
    - Abuse.ch feeds
    - Custom feeds
    """

    FEED_URLS = {
        "hagezi_threat": "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/domains/tif.txt",
        "cyberhost_malware": "https://lists.cyberhost.uk/malware.txt",
        "abuse_urlhaus": "https://urlhaus.abuse.ch/downloads/text_online/",
        "abuse_feodo": "https://feodotracker.abuse.ch/downloads/ipblocklist.txt",
    }

    def __init__(self, blocklist: OSINTBlocklist):
        self.blocklist = blocklist
        self._last_update: dict[str, datetime] = {}

    async def update_from_feed(
        self,
        feed_name: str,
        feed_url: Optional[str] = None,
    ) -> int:
        """
        Update blocklist from external feed.

        Args:
            feed_name: Name of the feed
            feed_url: Optional custom URL (uses built-in if not provided)

        Returns:
            Number of entries added
        """
        import aiohttp

        url = feed_url or self.FEED_URLS.get(feed_name)
        if not url:
            return 0

        try:
            async with aiohttp.ClientSession() as session:
                async with session.get(url, timeout=30) as response:
                    if response.status != 200:
                        return 0

                    content = await response.text()
                    added = 0

                    for line in content.splitlines():
                        line = line.strip()
                        if line and not line.startswith("#"):
                            # Add as medium severity malware
                            self.blocklist.add_domain(
                                domain=line,
                                category=ThreatCategory.MALWARE_DISTRIBUTION,
                                severity=ThreatSeverity.MEDIUM,
                                description=f"From {feed_name} feed",
                                source=feed_name,
                            )
                            added += 1

                    self._last_update[feed_name] = datetime.utcnow()
                    return added

        except Exception:
            return 0

    def get_feed_status(self) -> dict[str, Optional[str]]:
        """Get last update time for each feed."""
        return {
            name: self._last_update.get(name, None).isoformat()
            if name in self._last_update
            else None
            for name in self.FEED_URLS
        }


# =============================================================================
# USER-CONTROLLED BLOCKLIST MANAGER
# =============================================================================

class BlocklistManager:
    """
    User-controlled blocklist management with persistence.

    Provides:
    - On-demand updates from threat feeds
    - Manual domain addition/removal
    - Custom blocklist persistence
    - Feed subscription management
    - Import/export functionality

    Usage:
        manager = BlocklistManager()

        # Update from all feeds
        manager.update_all_feeds()

        # Add custom domain
        manager.add_custom_domain("malware.onion", "ransomware")

        # Remove domain
        manager.remove_domain("example.onion")

        # Save custom entries
        manager.save()
    """

    DEFAULT_CONFIG_DIR = Path.home() / ".groknroll" / "osint"

    def __init__(
        self,
        config_dir: Optional[Path] = None,
        auto_load: bool = True,
    ):
        """
        Initialize blocklist manager.

        Args:
            config_dir: Directory for persistent storage
            auto_load: Auto-load saved custom entries
        """
        self.config_dir = Path(config_dir) if config_dir else self.DEFAULT_CONFIG_DIR
        self.config_dir.mkdir(parents=True, exist_ok=True)

        # File paths
        self._custom_blocklist_path = self.config_dir / "custom_blocklist.json"
        self._feed_cache_path = self.config_dir / "feed_cache.json"
        self._config_path = self.config_dir / "blocklist_config.json"

        # Initialize blocklist with custom entries
        self._blocklist = OSINTBlocklist(
            custom_blocklist_path=self._custom_blocklist_path if auto_load else None
        )

        # Custom entries (user-added)
        self._custom_entries: list[BlockedDomain] = []

        # Feed configuration
        self._feed_config: dict = {
            "enabled_feeds": ["hagezi_threat", "abuse_urlhaus"],
            "auto_update": False,
            "update_interval_hours": 24,
            "last_update": None,
        }

        # Load configuration
        if auto_load:
            self._load_config()
            self._load_custom_entries()

    def _load_config(self) -> None:
        """Load configuration from file."""
        if self._config_path.exists():
            try:
                with open(self._config_path) as f:
                    self._feed_config.update(json.load(f))
            except Exception:
                pass

    def _save_config(self) -> None:
        """Save configuration to file."""
        with open(self._config_path, "w") as f:
            json.dump(self._feed_config, f, indent=2, default=str)

    def _load_custom_entries(self) -> None:
        """Load custom blocklist entries."""
        if self._custom_blocklist_path.exists():
            try:
                with open(self._custom_blocklist_path) as f:
                    data = json.load(f)
                    for entry in data.get("domains", []):
                        self._custom_entries.append(
                            BlockedDomain(
                                domain=entry["domain"],
                                category=ThreatCategory(entry.get("category", "malware_distribution")),
                                severity=ThreatSeverity(entry.get("severity", "medium")),
                                description=entry.get("description", "User-added entry"),
                                source="custom",
                                threat_actor=entry.get("threat_actor"),
                                tags=entry.get("tags", ["custom"]),
                            )
                        )
            except Exception:
                pass

    def save(self) -> bool:
        """
        Save custom entries and configuration.

        Returns:
            True if saved successfully
        """
        try:
            # Save custom entries
            data = {
                "version": "1.0",
                "updated": datetime.utcnow().isoformat(),
                "domains": [
                    {
                        "domain": e.domain,
                        "category": e.category.value,
                        "severity": e.severity.value,
                        "description": e.description,
                        "threat_actor": e.threat_actor,
                        "tags": e.tags,
                    }
                    for e in self._custom_entries
                ],
            }

            with open(self._custom_blocklist_path, "w") as f:
                json.dump(data, f, indent=2)

            # Save config
            self._save_config()
            return True

        except Exception:
            return False

    def add_custom_domain(
        self,
        domain: str,
        category: str = "malware_distribution",
        severity: str = "medium",
        description: str = "",
        threat_actor: Optional[str] = None,
        tags: Optional[list[str]] = None,
    ) -> bool:
        """
        Add a custom domain to the blocklist.

        Args:
            domain: Domain or regex pattern (prefix with "regex:")
            category: Threat category name
            severity: Severity level (critical, high, medium, low)
            description: Description of the threat
            threat_actor: Associated threat actor
            tags: Tags for the entry

        Returns:
            True if added successfully
        """
        try:
            # Validate category
            try:
                cat = ThreatCategory(category)
            except ValueError:
                cat = ThreatCategory.MALWARE_DISTRIBUTION

            # Validate severity
            try:
                sev = ThreatSeverity(severity)
            except ValueError:
                sev = ThreatSeverity.MEDIUM

            entry = BlockedDomain(
                domain=domain.lower().strip(),
                category=cat,
                severity=sev,
                description=description or f"User-added: {domain}",
                source="custom",
                threat_actor=threat_actor,
                tags=tags or ["custom"],
            )

            # Add to custom entries
            self._custom_entries.append(entry)

            # Add to active blocklist
            self._blocklist.add_domain(
                domain=entry.domain,
                category=entry.category,
                severity=entry.severity,
                description=entry.description,
                source=entry.source,
                threat_actor=entry.threat_actor,
                tags=entry.tags,
            )

            return True

        except Exception:
            return False

    def remove_domain(self, domain: str) -> bool:
        """
        Remove a domain from the custom blocklist.

        Note: Only removes custom entries, not built-in ones.

        Args:
            domain: Domain to remove

        Returns:
            True if removed
        """
        domain = domain.lower().strip()

        # Remove from custom entries
        original_len = len(self._custom_entries)
        self._custom_entries = [e for e in self._custom_entries if e.domain != domain]

        if len(self._custom_entries) < original_len:
            # Rebuild blocklist
            self._rebuild_blocklist()
            return True

        return False

    def _rebuild_blocklist(self) -> None:
        """Rebuild the blocklist with current custom entries."""
        # Create new blocklist
        self._blocklist = OSINTBlocklist()

        # Add custom entries
        for entry in self._custom_entries:
            self._blocklist.add_domain(
                domain=entry.domain,
                category=entry.category,
                severity=entry.severity,
                description=entry.description,
                source=entry.source,
                threat_actor=entry.threat_actor,
                tags=entry.tags,
            )

    def update_feed(self, feed_name: str) -> dict:
        """
        Update from a specific threat feed.

        Args:
            feed_name: Name of the feed to update

        Returns:
            Update result dict
        """
        import requests

        feed_urls = ThreatFeedIntegration.FEED_URLS

        if feed_name not in feed_urls:
            return {
                "success": False,
                "feed": feed_name,
                "error": f"Unknown feed: {feed_name}. Available: {list(feed_urls.keys())}",
                "added": 0,
            }

        url = feed_urls[feed_name]

        try:
            response = requests.get(url, timeout=60)
            if response.status_code != 200:
                return {
                    "success": False,
                    "feed": feed_name,
                    "error": f"HTTP {response.status_code}",
                    "added": 0,
                }

            added = 0
            for line in response.text.splitlines():
                line = line.strip()
                if line and not line.startswith("#") and not line.startswith("//"):
                    # Skip IP addresses for domain feeds
                    if re.match(r"^\d+\.\d+\.\d+\.\d+", line):
                        continue

                    self._blocklist.add_domain(
                        domain=line,
                        category=ThreatCategory.MALWARE_DISTRIBUTION,
                        severity=ThreatSeverity.MEDIUM,
                        description=f"From {feed_name} feed",
                        source=feed_name,
                        tags=["feed", feed_name],
                    )
                    added += 1

            # Update config
            self._feed_config["last_update"] = datetime.utcnow().isoformat()
            self._save_config()

            return {
                "success": True,
                "feed": feed_name,
                "url": url,
                "added": added,
                "timestamp": datetime.utcnow().isoformat(),
            }

        except requests.Timeout:
            return {
                "success": False,
                "feed": feed_name,
                "error": "Request timeout",
                "added": 0,
            }
        except Exception as e:
            return {
                "success": False,
                "feed": feed_name,
                "error": str(e),
                "added": 0,
            }

    def update_all_feeds(self) -> list[dict]:
        """
        Update from all enabled threat feeds.

        Returns:
            List of update results
        """
        results = []

        for feed_name in self._feed_config.get("enabled_feeds", []):
            result = self.update_feed(feed_name)
            results.append(result)

        return results

    def enable_feed(self, feed_name: str) -> bool:
        """Enable a threat feed for updates."""
        if feed_name not in ThreatFeedIntegration.FEED_URLS:
            return False

        if feed_name not in self._feed_config["enabled_feeds"]:
            self._feed_config["enabled_feeds"].append(feed_name)
            self._save_config()

        return True

    def disable_feed(self, feed_name: str) -> bool:
        """Disable a threat feed."""
        if feed_name in self._feed_config["enabled_feeds"]:
            self._feed_config["enabled_feeds"].remove(feed_name)
            self._save_config()
            return True
        return False

    def list_feeds(self) -> dict:
        """
        List available threat feeds and their status.

        Returns:
            Dict with feed information
        """
        feeds = {}
        available = ThreatFeedIntegration.FEED_URLS
        enabled = self._feed_config.get("enabled_feeds", [])

        for name, url in available.items():
            feeds[name] = {
                "url": url,
                "enabled": name in enabled,
            }

        return {
            "feeds": feeds,
            "last_update": self._feed_config.get("last_update"),
            "auto_update": self._feed_config.get("auto_update", False),
        }

    def add_custom_feed(self, name: str, url: str) -> bool:
        """
        Add a custom threat feed.

        Args:
            name: Feed name
            url: Feed URL

        Returns:
            True if added
        """
        # Add to ThreatFeedIntegration
        ThreatFeedIntegration.FEED_URLS[name] = url
        return self.enable_feed(name)

    def list_custom_entries(self) -> list[dict]:
        """List all custom blocklist entries."""
        return [
            {
                "domain": e.domain,
                "category": e.category.value,
                "severity": e.severity.value,
                "description": e.description,
                "threat_actor": e.threat_actor,
                "tags": e.tags,
            }
            for e in self._custom_entries
        ]

    def import_blocklist(self, path: Path) -> dict:
        """
        Import blocklist from file.

        Supports JSON and plain text formats.

        Args:
            path: Path to blocklist file

        Returns:
            Import result dict
        """
        if not path.exists():
            return {"success": False, "error": "File not found", "imported": 0}

        imported = 0

        try:
            with open(path) as f:
                content = f.read()

            # Try JSON format first
            try:
                data = json.loads(content)
                domains = data.get("domains", [])

                for entry in domains:
                    if isinstance(entry, dict):
                        self.add_custom_domain(
                            domain=entry.get("domain", ""),
                            category=entry.get("category", "malware_distribution"),
                            severity=entry.get("severity", "medium"),
                            description=entry.get("description", "Imported"),
                            threat_actor=entry.get("threat_actor"),
                            tags=entry.get("tags", ["imported"]),
                        )
                        imported += 1
                    elif isinstance(entry, str):
                        self.add_custom_domain(domain=entry)
                        imported += 1

            except json.JSONDecodeError:
                # Plain text format - one domain per line
                for line in content.splitlines():
                    line = line.strip()
                    if line and not line.startswith("#"):
                        self.add_custom_domain(domain=line)
                        imported += 1

            return {
                "success": True,
                "imported": imported,
                "source": str(path),
            }

        except Exception as e:
            return {"success": False, "error": str(e), "imported": 0}

    def export_blocklist(self, path: Path, include_feeds: bool = False) -> bool:
        """
        Export blocklist to file.

        Args:
            path: Output path
            include_feeds: Include feed-sourced entries

        Returns:
            True if exported
        """
        try:
            if include_feeds:
                self._blocklist.export_blocklist(path)
            else:
                # Export only custom entries
                data = {
                    "version": "1.0",
                    "exported": datetime.utcnow().isoformat(),
                    "domains": [
                        {
                            "domain": e.domain,
                            "category": e.category.value,
                            "severity": e.severity.value,
                            "description": e.description,
                            "threat_actor": e.threat_actor,
                            "tags": e.tags,
                        }
                        for e in self._custom_entries
                    ],
                }

                with open(path, "w") as f:
                    json.dump(data, f, indent=2)

            return True

        except Exception:
            return False

    def get_stats(self) -> dict:
        """Get blocklist statistics."""
        base_stats = self._blocklist.get_stats()
        base_stats["custom_entries"] = len(self._custom_entries)
        base_stats["enabled_feeds"] = len(self._feed_config.get("enabled_feeds", []))
        base_stats["last_feed_update"] = self._feed_config.get("last_update")
        return base_stats

    def check_url(self, url: str) -> Optional[dict]:
        """
        Check if URL is blocked.

        Args:
            url: URL to check

        Returns:
            Threat info dict or None if safe
        """
        result = self._blocklist.check_url(url)
        if result:
            return result.to_dict()
        return None

    def is_blocked(self, url: str) -> bool:
        """Check if URL is blocked."""
        return self._blocklist.is_blocked(url)

    @property
    def blocklist(self) -> OSINTBlocklist:
        """Get the underlying blocklist."""
        return self._blocklist


# =============================================================================
# CONVENIENCE FUNCTIONS
# =============================================================================

# Global blocklist instance
_blocklist: Optional[OSINTBlocklist] = None

# Global manager instance
_manager: Optional[BlocklistManager] = None


def get_blocklist() -> OSINTBlocklist:
    """Get the global blocklist instance."""
    global _blocklist
    if _blocklist is None:
        _blocklist = OSINTBlocklist()
    return _blocklist


def get_blocklist_manager() -> BlocklistManager:
    """Get the global blocklist manager instance."""
    global _manager
    if _manager is None:
        _manager = BlocklistManager()
    return _manager


def is_url_safe(url: str) -> bool:
    """Quick check if URL is safe to visit."""
    return not get_blocklist().is_blocked(url)


def check_url_threat(url: str) -> Optional[dict]:
    """
    Check URL and return threat info if blocked.

    Returns:
        Dict with threat info or None if safe
    """
    result = get_blocklist().check_url(url)
    if result:
        return result.to_dict()
    return None
