"""
Bulk-specific validation utilities.

Provides validation for:
- Unique field presence
- Duplicate detection
- Batch size limits
- Permission checks
"""

from .exceptions import MissingUniqueFieldError, DuplicateKeyError, BatchSizeExceededError, BulkValidationError
from .utils import build_unique_key


class BulkValidationMixin:
    """
    Validation utilities for bulk operations.
    Mixin for use in serializers or operations.
    """

    def validate_unique_fields_present(self, data, unique_fields):
        """
        Ensure all unique_fields exist in each data item.

        Args:
            data: List of data dicts
            unique_fields: List of required unique field names

        Raises:
            MissingUniqueFieldError: If any unique fields are missing
        """
        missing_indices = []
        missing_fields = set()

        for idx, item in enumerate(data):
            for field in unique_fields:
                if field not in item or item[field] is None:
                    missing_indices.append(idx)
                    missing_fields.add(field)

        if missing_indices:
            raise MissingUniqueFieldError(
                field=", ".join(missing_fields),
                indices=missing_indices,
                detail=f"Unique fields {missing_fields} missing at indices: {missing_indices}",
            )

    def validate_no_duplicate_keys(self, validated_data, unique_fields):
        """
        Check for duplicate unique_field combinations in request.

        Args:
            validated_data: List of validated data dicts
            unique_fields: List of unique field names

        Raises:
            DuplicateKeyError: If duplicates found
        """
        seen_keys = {}
        duplicates = []

        for idx, item in enumerate(validated_data):
            key = build_unique_key(item, unique_fields)

            if key in seen_keys:
                duplicates.append({"key": dict(zip(unique_fields, key)), "indices": [seen_keys[key], idx]})
            else:
                seen_keys[key] = idx

        if duplicates:
            raise DuplicateKeyError(duplicates)

    def validate_batch_size(self, data, max_size):
        """
        Ensure request doesn't exceed MAX_BATCH_SIZE.

        Args:
            data: List of data items
            max_size: Maximum allowed batch size

        Raises:
            BatchSizeExceededError: If batch is too large
        """
        actual_size = len(data) if isinstance(data, list) else 1

        if actual_size > max_size:
            raise BatchSizeExceededError(size=actual_size, max_size=max_size)

    def validate_update_permissions(self, user, instances):
        """
        Check user has permission to update all instances.

        This is a hook for custom permission logic.
        Override in subclasses to implement specific permission checks.

        Args:
            user: User making the request
            instances: List of instances to update

        Returns:
            True if user has permission, False otherwise
        """
        # Default: allow all (override in subclasses for custom logic)
        return True


class BulkDataValidator:
    """
    Validates bulk request data structure and constraints.
    """

    def __init__(self, unique_fields, max_batch_size):
        """
        Initialize with validation configuration.

        Args:
            unique_fields: List of unique field names
            max_batch_size: Maximum allowed batch size
        """
        self.unique_fields = unique_fields
        self.max_batch_size = max_batch_size
        self.errors = {}

    def validate_structure(self, data):
        """
        Validate data is list and each item is dict.

        Args:
            data: Request data

        Returns:
            Cleaned data

        Raises:
            BulkValidationError: If structure is invalid
        """
        if not isinstance(data, list):
            raise BulkValidationError(errors={"_": "Data must be a list for bulk operations"}, detail="Invalid data structure")

        for idx, item in enumerate(data):
            if not isinstance(item, dict):
                self.errors[str(idx)] = "Each item must be a dictionary"

        if self.errors:
            raise BulkValidationError(errors=self.errors)

        return data

    def validate_unique_constraints(self, validated_data):
        """
        Check unique_field constraints across all items.

        Args:
            validated_data: List of validated data dicts

        Raises:
            MissingUniqueFieldError: If required fields missing
            DuplicateKeyError: If duplicate keys found
        """
        # Check for missing unique fields
        self._check_missing_unique_fields(validated_data)

        # Check for duplicates
        self._check_duplicates(validated_data)

    def _check_missing_unique_fields(self, validated_data):
        """
        Check for missing unique fields.

        Args:
            validated_data: List of validated data dicts
        """
        missing_map = {}  # field -> [indices]

        for idx, item in enumerate(validated_data):
            for field in self.unique_fields:
                if field not in item or item[field] is None:
                    if field not in missing_map:
                        missing_map[field] = []
                    missing_map[field].append(idx)

        if missing_map:
            # Report first missing field
            field, indices = next(iter(missing_map.items()))
            raise MissingUniqueFieldError(field=field, indices=indices)

    def _check_duplicates(self, validated_data):
        """
        Check for duplicate unique keys.

        Args:
            validated_data: List of validated data dicts
        """
        seen_keys = {}
        duplicates = []

        for idx, item in enumerate(validated_data):
            key = build_unique_key(item, self.unique_fields)

            if key in seen_keys:
                duplicates.append({"key": dict(zip(self.unique_fields, key)), "indices": [seen_keys[key], idx]})
            else:
                seen_keys[key] = idx

        if duplicates:
            raise DuplicateKeyError(duplicates)

    def collect_errors(self):
        """
        Aggregate all validation errors.

        Returns:
            Dict of {index: errors}
        """
        return self.errors


def validate_bulk_request(data, unique_fields, max_batch_size):
    """
    Convenience function for validating bulk requests.

    Performs:
    - Structure validation
    - Batch size check
    - Unique field presence
    - Duplicate detection

    Args:
        data: Request data
        unique_fields: List of unique field names
        max_batch_size: Maximum allowed batch size

    Returns:
        Validated data

    Raises:
        BulkValidationError: If validation fails
        BatchSizeExceededError: If batch too large
        MissingUniqueFieldError: If unique fields missing
        DuplicateKeyError: If duplicates found
    """
    validator = BulkDataValidator(unique_fields, max_batch_size)

    # Validate structure
    data = validator.validate_structure(data)

    # Validate batch size
    if len(data) > max_batch_size:
        raise BatchSizeExceededError(size=len(data), max_size=max_batch_size)

    # Validate unique constraints
    validator.validate_unique_constraints(data)

    return data


def validate_for_update(data, unique_fields):
    """
    Validate data for update operation.

    Update requires all unique fields to be present.

    Args:
        data: List of data dicts
        unique_fields: List of unique field names

    Raises:
        MissingUniqueFieldError: If unique fields missing
    """
    missing_map = {}

    for idx, item in enumerate(data):
        for field in unique_fields:
            if field not in item:
                if field not in missing_map:
                    missing_map[field] = []
                missing_map[field].append(idx)

    if missing_map:
        field, indices = next(iter(missing_map.items()))
        raise MissingUniqueFieldError(
            field=field, indices=indices, detail=f'Update operation requires field "{field}" at indices: {indices}'
        )


def validate_for_delete(data, unique_fields):
    """
    Validate data for delete operation.

    Delete requires at least one unique field per item.

    Args:
        data: List of data dicts
        unique_fields: List of unique field names

    Raises:
        BulkValidationError: If no unique fields present
    """
    errors = {}

    for idx, item in enumerate(data):
        has_any_unique = any(field in item and item[field] is not None for field in unique_fields)
        if not has_any_unique:
            errors[str(idx)] = f"At least one unique field required: {unique_fields}"

    if errors:
        raise BulkValidationError(errors=errors, detail="Delete operation requires unique field identifiers")
