"""Defanging — IOC obfuscation styles common in real CTI prose.

Real advisories don't write `1.2.3.4` and `http://evil.com` verbatim; they
defang IOCs into bracket/paren/word forms (`1[.]2[.]3[.]4`, `hxxp://evil[.]com`)
so indicators don't auto-link in email/chat clients. Training the IOC extractor
purely on canonical inputs leaves it brittle. This module corrupts canonical
IOCs into common defanged forms for use as training-time data augmentation.
"""

from __future__ import annotations

import random
from typing import List, Optional


# Dot replacement styles seen in the wild, in rough frequency order.
_DOT_FORMS = ("[.]", "(.)", "[dot]", "{.}")

_PROTOCOL_FORMS = {
    "http://": "hxxp://",
    "https://": "hxxps://",
}


def _rng(r: Optional[random.Random]) -> random.Random:
    return r if r is not None else random.Random()


def defang_ip(ip: str, rng: Optional[random.Random] = None) -> str:
    """`1.2.3.4` → `1[.]2[.]3[.]4` (one randomly chosen dot style)."""
    return ip.replace(".", _rng(rng).choice(_DOT_FORMS))


def defang_domain(domain: str, rng: Optional[random.Random] = None) -> str:
    """`evil.com` → `evil[.]com`."""
    return domain.replace(".", _rng(rng).choice(_DOT_FORMS))


def defang_url(url: str, rng: Optional[random.Random] = None) -> str:
    """`http://evil.com/x` → `hxxp://evil[.]com/x` — protocol + dot defanging."""
    r = _rng(rng)
    out = url
    for proto, replacement in _PROTOCOL_FORMS.items():
        if out.startswith(proto):
            out = replacement + out[len(proto):]
            break
    out = out.replace(".", r.choice(_DOT_FORMS))
    return out


def defang_text(
    text: str,
    ips: List[str],
    domains: List[str],
    urls: List[str],
    rng: Optional[random.Random] = None,
) -> str:
    """Defang every occurrence of the given IOCs inside a free-text body.

    URLs are replaced before domains (URLs contain domain substrings, so
    domain-first would corrupt the URL match). Likewise IPs last. Each
    occurrence picks its own dot-style independently — real advisories don't
    keep one style consistent across paragraphs.
    """
    r = _rng(rng)
    out = text
    for u in sorted(set(urls), key=len, reverse=True):
        out = out.replace(u, defang_url(u, r))
    for d in sorted(set(domains), key=len, reverse=True):
        out = out.replace(d, defang_domain(d, r))
    for i in sorted(set(ips), key=len, reverse=True):
        out = out.replace(i, defang_ip(i, r))
    return out
