"""Validation transformation plugin."""

import re
from typing import Optional, Any, Union
from decimal import Decimal

from ...base import ChainableTransformer
from ...types import TransformContext
from ...exceptions import ValidationError
from ..base import TransformerPlugin, register_transformer


@register_transformer("bool")
class BooleanTransformer(ChainableTransformer[str, bool]):
    """
    Description:
        A transformer that converts string values to boolean based on common
        true/false representations. Handles various string formats like 'true',
        'yes', '1', etc.

    Version: v1
    Status: Production
    Last Updated: 2024-03-24

    Args:
        name (str): Unique identifier for this transformer

    Returns:
        Optional[bool]: The boolean value (True, False) or None if conversion fails

    Raises:
        ValidationError: If the input value is not a string or boolean

    Notes:
        - True values: 'true', '1', 'yes', 'y', 't' (case-insensitive)
        - False values: 'false', '0', 'no', 'n', 'f' (case-insensitive)
        - Returns None for values that don't match any of the above
        - Passes through boolean inputs unchanged

    Example:
        ```python
        # Basic boolean conversion
        transformer = BooleanTransformer("to_bool")

        # Convert string to boolean
        result = transformer.transform("yes")
        assert result.value is True

        result = transformer.transform("NO")
        assert result.value is False

        # Handle numeric strings
        result = transformer.transform("1")
        assert result.value is True

        result = transformer.transform("0")
        assert result.value is False

        # Handle actual booleans
        result = transformer.transform(True)
        assert result.value is True

        # Handle invalid values
        result = transformer.transform("maybe")
        assert result.value is None

        # Chain with other transformers
        default = DefaultValueTransformer("default_false", default_value=False)
        pipeline = transformer.chain(default)

        result = pipeline.transform("maybe")  # Invalid value
        assert result.value is False  # Uses default
        ```
    """

    TRUE_VALUES = {'true', '1', 'yes', 'y', 't', "on", "si", "sí", "verdadero"}
    FALSE_VALUES = {'false', '0', 'no', 'n', 'f', "off", "falso"}

    def validate(self, value: str) -> bool:
        if isinstance(value, bool):
            return True
        return isinstance(value, str)

    def _transform(self, value: Any, context: Optional[TransformContext] = None) -> Optional[bool]:
        if isinstance(value, bool):
            return value

        val_str = str(value).strip().lower()
        if val_str in self.TRUE_VALUES:
            return True
        if val_str in self.FALSE_VALUES:
            return False
        return None

@register_transformer("email_validator")
class EmailValidator(ChainableTransformer[str, str]):
    """
    Description:
        A transformer that validates email addresses using a simple regex pattern
        and optional domain restrictions. Returns the email if valid or None if invalid.

    Version: v1
    Status: Production
    Last Updated: 2024-03-24

    Args:
        name (str): Unique identifier for this transformer
        allowed_domains (Optional[list]): List of allowed email domains (e.g., ['example.com'])

    Returns:
        Optional[str]: The validated email address or None if validation fails

    Raises:
        ValidationError: If the input value is not a string

    Notes:
        - Uses a basic regex pattern to validate email format
        - Optionally restricts to specific domains
        - Returns None for invalid emails instead of raising an exception
        - Trims whitespace from input

    Example:
        ```python
        # Basic email validation
        transformer = EmailValidator("validate_email")

        # Valid email
        result = transformer.transform("user@example.com")
        assert result.value == "user@example.com"

        # Invalid email
        result = transformer.transform("not-an-email")
        assert result.value is None

        # With domain restrictions
        restricted = EmailValidator(
            "company_email",
            allowed_domains=["company.com", "company.org"]
        )

        # Valid domain
        result = restricted.transform("user@company.com")
        assert result.value == "user@company.com"

        # Invalid domain
        result = restricted.transform("user@gmail.com")
        assert result.value is None

        # Chain with other transformers
        default = DefaultValueTransformer(
            "default_email",
            default_value="unknown@example.com"
        )
        pipeline = transformer.chain(default)

        result = pipeline.transform("invalid-email")
        assert result.value == "unknown@example.com"
        ```
    """

    def __init__(self, name: str, allowed_domains: Optional[list] = None):
        super().__init__(name)
        self.allowed_domains = allowed_domains
        self.pattern = re.compile(r'^[^@]+@[^@]+\.[^@]+$')

    def _transform(self, value: str, context: Optional[TransformContext] = None) -> Optional[str]:
        value = value.strip()
        if not self.pattern.match(value):
            return None

        if self.allowed_domains:
            domain = value.split('@')[1]
            if domain not in self.allowed_domains:
                return None

        return value

@register_transformer("phone_formatter", format="({area}) {prefix}-{line}")
class PhoneFormatter(ChainableTransformer[str, str]):
    """
    Description:
        A transformer that formats phone numbers according to a specified pattern.
        Extracts digits from the input and formats them into a standardized format.

    Version: v1
    Status: Production
    Last Updated: 2024-03-24

    Args:
        name (str): Unique identifier for this transformer
        format (str): Format string with placeholders for area code, prefix, and line
            (default: '({area}) {prefix}-{line}')

    Returns:
        str: The formatted phone number

    Raises:
        ValidationError: If the input value is not a string or doesn't contain
            exactly 10 digits (or 11 digits starting with '1')

    Notes:
        - Removes all non-digit characters from input
        - Handles US/Canada numbers (10 digits)
        - Automatically strips leading '1' from 11-digit numbers
        - Format string can use {area}, {prefix}, and {line} placeholders

    Example:
        ```python
        # Basic phone formatting
        transformer = PhoneFormatter("format_phone")

        # Format with default pattern: (XXX) XXX-XXXX
        result = transformer.transform("1234567890")
        assert result.value == "(123) 456-7890"

        # Handle input with non-digit characters
        result = transformer.transform("(123) 456-7890")
        assert result.value == "(123) 456-7890"

        # Handle 11-digit number with leading 1
        result = transformer.transform("11234567890")
        assert result.value == "(123) 456-7890"

        # Custom format
        custom = PhoneFormatter(
            "custom_format",
            format="{area}-{prefix}-{line}"
        )
        result = custom.transform("1234567890")
        assert result.value == "123-456-7890"

        # Chain with other transformers
        uppercase = UppercaseTransformer("uppercase")
        pipeline = custom.chain(uppercase)

        result = pipeline.transform("1234567890")
        assert result.value == "123-456-7890"  # No effect on digits
        ```
    """

    def __init__(self, name: str, format: str = '({area}) {prefix}-{line}'):
        super().__init__(name)
        self.format = format
        self.digit_pattern = re.compile(r'\D')

    def _transform(self, value: str, context: Optional[TransformContext] = None) -> str:
        digits = self.digit_pattern.sub('', value)

        if len(digits) == 11 and digits[0] == '1':
            digits = digits[1:]

        if len(digits) != 10:
            raise ValidationError("Invalid phone number length", value)

        return self.format.format(
            area=digits[:3],
            prefix=digits[3:6],
            line=digits[6:]
        )

@register_transformer("credit_card_check", mask=False)
class CreditCardValidator(ChainableTransformer[str, str]):
    """
    Description:
        A transformer that validates credit card numbers using the Luhn algorithm
        and optionally masks the number for security. Returns the validated number
        (original or masked) or None if invalid.

    Version: v1
    Status: Production
    Last Updated: 2024-03-24

    Args:
        name (str): Unique identifier for this transformer
        mask (bool): Whether to mask the middle digits of the card number (default: False)

    Returns:
        Optional[str]: The validated credit card number (original or masked) or None if invalid

    Raises:
        ValidationError: If the input value is not a string

    Notes:
        - Removes all non-digit characters from input
        - Validates using the Luhn algorithm (checksum)
        - Checks that length is between 13-19 digits (standard card lengths)
        - When masking, shows first and last 4 digits, masks the rest
        - Returns None for invalid card numbers instead of raising an exception

    Example:
        ```python
        # Basic credit card validation
        transformer = CreditCardValidator("validate_cc")

        # Valid card (test number)
        result = transformer.transform("4111 1111 1111 1111")
        assert result.value == "4111 1111 1111 1111"  # Returns original format

        # Invalid card
        result = transformer.transform("1234 5678 9012 3456")
        assert result.value is None

        # With masking
        masked = CreditCardValidator("masked_cc", mask=True)

        # Valid card with masking
        result = masked.transform("4111-1111-1111-1111")
        assert result.value == "4111********1111"

        # Handle input with spaces, dashes, etc.
        result = masked.transform("4111 1111 1111 1111")
        assert result.value == "4111********1111"

        # Chain with other transformers
        default = DefaultValueTransformer(
            "default_cc",
            default_value="INVALID"
        )
        pipeline = transformer.chain(default)

        result = pipeline.transform("1234 5678 9012 3456")  # Invalid
        assert result.value == "INVALID"
        ```
    """

    def __init__(self, name: str, mask: bool = False):
        super().__init__(name)
        self.mask = mask
        self.digit_pattern = re.compile(r'\D')

    def _transform(self, value: str, context: Optional[TransformContext] = None) -> Optional[str]:
        # Keep original format
        original = value
        # Extract digits
        digits = self.digit_pattern.sub('', value)

        if len(digits) < 13 or len(digits) > 19:
            return None

        # Luhn algorithm
        sum_ = 0
        alt = False
        for d in digits[::-1]:
            d = int(d)
            if alt:
                d *= 2
                if d > 9:
                    d -= 9
            sum_ += d
            alt = not alt

        if sum_ % 10 != 0:
            return None

        # Return masked or original number
        if self.mask:
            visible = 4
            masked_len = len(digits) - (2 * visible)
            return f"{digits[:visible]}{'*' * masked_len}{digits[-visible:]}"

        return original

class TypeEnforcer(ChainableTransformer[Any, Any]):
    """
    Description:
        A transformer that enforces type conversion of input values to a specified
        target type. Supports conversion to int, float, str, bool, and decimal types.

    Version: v1
    Status: Production
    Last Updated: 2024-03-24

    Args:
            name (str): Unique identifier for this transformer
            ``target_type`` (str): The target type to convert to ('int', 'float', 'str', 'bool', 'decimal')

    Returns:
        Any: The converted value in the target type

    Raises:
        ValidationError: If the conversion fails or if the target type is not supported

    Notes:
        - Supported target types: 'int', 'float', 'str', 'bool', 'decimal'
        - For 'int' conversion of string values, handles float strings (e.g., "10.5" → 10)
        - For 'bool' conversion of string values, uses common true/false representations
        - For 'decimal' conversion, uses Python's Decimal type for precise decimal arithmetic

    Example:
        ```python
        # Integer conversion
        transformer = TypeEnforcer("to_int", ``target_type``="int")

        # Convert string to int
        result = transformer.transform("123")
        assert result.value == 123

        # Convert float to int
        result = transformer.transform(45.67)
        assert result.value == 45

        # Float conversion
        float_converter = TypeEnforcer("to_float", ``target_type``="float")

        # Convert string to float
        result = float_converter.transform("123.45")
        assert result.value == 123.45

        # Boolean conversion
        bool_converter = TypeEnforcer("to_bool", ``target_type``="bool")

        # Convert string to bool
        result = bool_converter.transform("yes")
        assert result.value is True

        # Decimal conversion
        decimal_converter = TypeEnforcer("to_decimal", ``target_type``="decimal")

        # Convert string to Decimal
        result = decimal_converter.transform("123.45")
        assert str(result.value) == "123.45"

        # Chain with other transformers
        round_transformer = RoundTransformer("round", decimals=2)
        pipeline = float_converter.chain(round_transformer)

        result = pipeline.transform("123.456")
        assert result.value == 123.46
        ```
    """

    def __init__(self, name: str, target_type: str):
        super().__init__(name)
        self.target_type = target_type

    def _transform(self, value: Any, context: Optional[TransformContext] = None) -> Any:
        try:
            if self.target_type == 'int':
                if isinstance(value, str):
                    # Handle float strings
                    return int(float(value))
                return int(value)
            elif self.target_type == 'float':
                return float(value)
            elif self.target_type == 'str':
                return str(value)
            elif self.target_type == 'bool':
                if isinstance(value, str):
                    value = value.lower()
                    if value in ('true', '1', 'yes', 'y'):
                        return True
                    if value in ('false', '0', 'no', 'n'):
                        return False
                return bool(value)
            elif self.target_type == 'decimal':
                if isinstance(value, str):
                    return Decimal(value)
                return Decimal(str(value))
            else:
                raise ValidationError(f"Unsupported type: {self.target_type}", value)
        except (ValueError, TypeError, ArithmeticError) as e:
            raise ValidationError(f"Type conversion failed: {str(e)}", value)


class ValidationTransformersPlugin(TransformerPlugin):
    """Plugin providing validation transformation capabilities."""

    def __init__(self):
        """Initialize the validation transformers plugin."""
        super().__init__("validation")

    @property
    def transformers(self):
        """Get the validation transformers."""
        result = self._auto_transformers()
        # type_enforcer needs param rename: 'type' -> 'target_type'
        result['type_enforcer'] = lambda params: TypeEnforcer('type_enforcer',
            target_type=params.get('type'))
        return result

    @property
    def manifest(self):
        from ...manifest import PluginManifest
        return PluginManifest(
            name="validation",
            display_name="Validation",
            description="Email, phone, credit card validation and type enforcement.",
            icon="check-circle",
            group="Data",
        )
