# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

import logging
from typing import ClassVar

from pyrit.converter.text_selection_strategy import WordSelectionStrategy
from pyrit.converter.word_level_converter import WordLevelConverter
from pyrit.models import ComponentIdentifier

logger = logging.getLogger(__name__)


class ZalgoConverter(WordLevelConverter):
    """
    Converts text into cursed Zalgo text using combining Unicode marks.
    """

    # Unicode combining characters for Zalgo effect (U+0300–U+036F)
    ZALGO_MARKS: ClassVar[list[str]] = [chr(code) for code in range(0x0300, 0x036F + 1)]
    # Setting a max intensity so people don't do anything unreasonable
    MAX_INTENSITY: ClassVar[int] = 100

    def __init__(
        self,
        *,
        intensity: int = 10,
        seed: int | None = None,
        word_selection_strategy: WordSelectionStrategy | None = None,
    ) -> None:
        """
        Initialize the converter with the specified selection parameters.

        Args:
            intensity (int): Number of combining marks per character (higher = more cursed). Default is 10.
            seed (int | None): Optional root seed for this conversion. Nested components inherit
                independent child streams unless they provide their own seed. If omitted, the converter
                inherits the root configured by ``initialize_pyrit_async(seed=...)``.
            word_selection_strategy (WordSelectionStrategy | None): Strategy for selecting which words to convert.
                If None, all words will be converted.
        """
        super().__init__(word_selection_strategy=word_selection_strategy)
        self._intensity = self._normalize_intensity(intensity)
        self._seed = seed

    def _build_identifier(self) -> ComponentIdentifier:
        """
        Build the converter identifier with zalgo parameters.

        Returns:
            ComponentIdentifier: The identifier for this converter.
        """
        return self._create_identifier(
            params={
                "intensity": self._intensity,
            },
        )

    def _normalize_intensity(self, intensity: int) -> int:
        try:
            intensity = int(intensity)
        except (TypeError, ValueError):
            raise ValueError(f"Invalid intensity value: {intensity!r} (must be an integer)") from None

        normalized_intensity = max(0, min(intensity, self.MAX_INTENSITY))
        if intensity != normalized_intensity:
            logger.warning(
                f"ZalgoConverter supports intensity between 0 and {self.MAX_INTENSITY}, "
                f"but received a value of {intensity}. Normalizing to {normalized_intensity}."
            )
        return normalized_intensity

    async def convert_word_async(self, word: str) -> str:
        """
        Convert a single word into the target format supported by the converter.

        Args:
            word (str): The word to be converted.

        Returns:
            str: The converted word.
        """
        if self._intensity <= 0:
            return word

        rng = self._get_random_generator(stream="combining-marks")

        def glitch(char: str) -> str:
            return char + "".join(rng.choice(self.ZALGO_MARKS) for _ in range(rng.randint(1, self._intensity)))

        return "".join(glitch(c) if c.isalnum() else c for c in word)
