"""
Worktable Service - Validators.

Validation logic for:
- Table schema validation
- Record data validation
- Column configuration validation
"""
import re
from datetime import date, datetime
from decimal import Decimal, InvalidOperation
from typing import Any
from urllib.parse import urlparse

from django.core.validators import validate_email, URLValidator
from django.utils.translation import gettext_lazy as _

from .models import FieldType, TableColumn


class ValidationError(Exception):
    """Custom validation error with field context."""
    
    def __init__(self, message: str, field: str = None, code: str = None):
        self.message = message
        self.field = field
        self.code = code or 'invalid'
        super().__init__(message)
    
    def to_dict(self):
        return {
            'field': self.field,
            'message': str(self.message),
            'code': self.code,
        }


class SchemaValidator:
    """
    Validates table schema (column definitions).
    """
    
    RESERVED_SLUGS = {'id', 'pk', 'created_at', 'updated_at', 'owner', 'tenant'}
    SLUG_PATTERN = re.compile(r'^[a-z][a-z0-9_]*$')
    
    @classmethod
    def validate_column_slug(cls, slug: str, existing_slugs: set = None) -> None:
        """
        Validate column slug.
        
        Args:
            slug: The slug to validate
            existing_slugs: Set of existing slugs to check for duplicates
        """
        if not slug:
            raise ValidationError(
                _('شناسه ستون الزامی است'),
                field='slug',
                code='required'
            )
        
        if not cls.SLUG_PATTERN.match(slug):
            raise ValidationError(
                _('شناسه باید با حروف کوچک شروع شود و فقط شامل حروف، اعداد و _ باشد'),
                field='slug',
                code='invalid_format'
            )
        
        if slug in cls.RESERVED_SLUGS:
            raise ValidationError(
                _('این شناسه رزرو شده است'),
                field='slug',
                code='reserved'
            )
        
        if existing_slugs and slug in existing_slugs:
            raise ValidationError(
                _('این شناسه قبلاً استفاده شده است'),
                field='slug',
                code='duplicate'
            )
    
    @classmethod
    def validate_column_config(cls, field_type: str, config: dict) -> dict:
        """
        Validate and normalize column configuration based on field type.
        
        Returns validated and normalized config.
        """
        validated = {}
        
        if field_type == FieldType.TEXT:
            validated['min_length'] = config.get('min_length', 0)
            validated['max_length'] = config.get('max_length', 255)
            validated['pattern'] = config.get('pattern')
            
            if validated['min_length'] > validated['max_length']:
                raise ValidationError(
                    _('حداقل طول نمی‌تواند بیشتر از حداکثر باشد'),
                    field='config.min_length',
                    code='invalid_range'
                )
        
        elif field_type == FieldType.TEXTAREA:
            validated['max_length'] = config.get('max_length', 5000)
            validated['rows'] = config.get('rows', 4)
        
        elif field_type in (FieldType.NUMBER, FieldType.DECIMAL):
            validated['min'] = config.get('min')
            validated['max'] = config.get('max')
            
            if validated['min'] is not None and validated['max'] is not None:
                if validated['min'] > validated['max']:
                    raise ValidationError(
                        _('حداقل نمی‌تواند بیشتر از حداکثر باشد'),
                        field='config.min',
                        code='invalid_range'
                    )
            
            if field_type == FieldType.DECIMAL:
                validated['precision'] = config.get('precision', 2)
        
        elif field_type == FieldType.SELECT:
            options = config.get('options', [])
            if not options:
                raise ValidationError(
                    _('گزینه‌های انتخاب الزامی است'),
                    field='config.options',
                    code='required'
                )
            validated['options'] = cls._validate_select_options(options)
        
        elif field_type == FieldType.MULTISELECT:
            options = config.get('options', [])
            if not options:
                raise ValidationError(
                    _('گزینه‌های انتخاب الزامی است'),
                    field='config.options',
                    code='required'
                )
            validated['options'] = cls._validate_select_options(options)
            validated['max_selections'] = config.get('max_selections')
        
        elif field_type == FieldType.RELATION:
            related_table = config.get('related_table')
            if not related_table:
                raise ValidationError(
                    _('جدول مرتبط الزامی است'),
                    field='config.related_table',
                    code='required'
                )
            validated['related_table'] = related_table
            validated['display_field'] = config.get('display_field', 'name')
            validated['multiple'] = config.get('multiple', False)
        
        elif field_type == FieldType.FILE:
            validated['allowed_extensions'] = config.get('allowed_extensions', [])
            validated['max_size'] = config.get('max_size', 10 * 1024 * 1024)  # 10MB default
            validated['multiple'] = config.get('multiple', False)
        
        elif field_type == FieldType.IMAGE:
            validated['allowed_extensions'] = config.get('allowed_extensions', ['jpg', 'jpeg', 'png', 'gif', 'webp'])
            validated['max_size'] = config.get('max_size', 5 * 1024 * 1024)  # 5MB default
            validated['max_width'] = config.get('max_width')
            validated['max_height'] = config.get('max_height')
        
        elif field_type == FieldType.COMPUTED:
            expression = config.get('expression')
            if not expression:
                raise ValidationError(
                    _('فرمول محاسبه الزامی است'),
                    field='config.expression',
                    code='required'
                )
            validated['expression'] = expression
            validated['dependencies'] = config.get('dependencies', [])
            validated['result_type'] = config.get('result_type', FieldType.TEXT)
        
        elif field_type == FieldType.DATE:
            validated['min_date'] = config.get('min_date')
            validated['max_date'] = config.get('max_date')
            validated['format'] = config.get('format', 'YYYY-MM-DD')
        
        elif field_type == FieldType.DATETIME:
            validated['min_datetime'] = config.get('min_datetime')
            validated['max_datetime'] = config.get('max_datetime')
            validated['format'] = config.get('format', 'YYYY-MM-DD HH:mm')
        
        # Common config options
        validated['placeholder'] = config.get('placeholder', '')
        validated['help_text'] = config.get('help_text', '')
        validated['default'] = config.get('default')
        
        return validated
    
    @classmethod
    def _validate_select_options(cls, options: list) -> list:
        """
        Validate and normalize select options.
        
        Options can be:
        - List of strings: ['option1', 'option2']
        - List of dicts: [{'value': 'opt1', 'label': 'Option 1'}]
        """
        validated = []
        seen_values = set()
        
        for opt in options:
            if isinstance(opt, str):
                if opt in seen_values:
                    raise ValidationError(
                        _('گزینه تکراری: {}').format(opt),
                        field='config.options',
                        code='duplicate_option'
                    )
                seen_values.add(opt)
                validated.append({'value': opt, 'label': opt})
            elif isinstance(opt, dict):
                value = opt.get('value')
                label = opt.get('label', value)
                if not value:
                    raise ValidationError(
                        _('مقدار گزینه الزامی است'),
                        field='config.options',
                        code='invalid_option'
                    )
                if value in seen_values:
                    raise ValidationError(
                        _('گزینه تکراری: {}').format(value),
                        field='config.options',
                        code='duplicate_option'
                    )
                seen_values.add(value)
                validated.append({'value': value, 'label': label})
            else:
                raise ValidationError(
                    _('فرمت گزینه نامعتبر است'),
                    field='config.options',
                    code='invalid_option_format'
                )
        
        return validated


class DataValidator:
    """
    Validates record data against table schema.
    """
    
    def __init__(self, table_columns: list[TableColumn]):
        """
        Initialize validator with table columns.
        
        Args:
            table_columns: List of TableColumn instances
        """
        self.columns = {col.slug: col for col in table_columns}
    
    def validate(self, data: dict, partial: bool = False) -> dict:
        """
        Validate record data.
        
        Args:
            data: The data to validate
            partial: If True, allow missing required fields (for PATCH)
        
        Returns:
            Validated and cleaned data
        
        Raises:
            ValidationError: If validation fails
        """
        errors = []
        validated_data = {}
        
        # Check required fields
        if not partial:
            for slug, column in self.columns.items():
                if column.is_required and slug not in data:
                    if column.config.get('default') is None:
                        errors.append(ValidationError(
                            _('این فیلد الزامی است'),
                            field=slug,
                            code='required'
                        ))
        
        # Validate provided fields
        for slug, value in data.items():
            column = self.columns.get(slug)
            if not column:
                # Skip unknown fields (or you could add them to errors)
                continue
            
            try:
                validated_data[slug] = self._validate_field(column, value)
            except ValidationError as e:
                e.field = slug
                errors.append(e)
        
        if errors:
            raise MultiValidationError(errors)
        
        return validated_data
    
    def _validate_field(self, column: TableColumn, value: Any) -> Any:
        """
        Validate a single field value.
        
        Returns cleaned/coerced value.
        """
        # Handle null/empty values
        if value is None or value == '':
            if column.is_required:
                raise ValidationError(
                    _('این فیلد الزامی است'),
                    code='required'
                )
            return None
        
        field_type = column.field_type
        config = column.config or {}
        
        validators = {
            FieldType.TEXT: self._validate_text,
            FieldType.TEXTAREA: self._validate_textarea,
            FieldType.NUMBER: self._validate_number,
            FieldType.DECIMAL: self._validate_decimal,
            FieldType.DATE: self._validate_date,
            FieldType.DATETIME: self._validate_datetime,
            FieldType.BOOLEAN: self._validate_boolean,
            FieldType.SELECT: self._validate_select,
            FieldType.MULTISELECT: self._validate_multiselect,
            FieldType.EMAIL: self._validate_email,
            FieldType.URL: self._validate_url,
            FieldType.PHONE: self._validate_phone,
            FieldType.JSON: self._validate_json,
            FieldType.FILE: self._validate_file,
            FieldType.IMAGE: self._validate_image,
            FieldType.RELATION: self._validate_relation,
        }
        
        validator = validators.get(field_type)
        if validator:
            return validator(value, config)
        
        # For computed fields, just return None (computed at read time)
        if field_type == FieldType.COMPUTED:
            return None
        
        return value
    
    def _validate_text(self, value: Any, config: dict) -> str:
        """Validate text field."""
        value = str(value)
        
        min_length = config.get('min_length', 0)
        max_length = config.get('max_length', 255)
        pattern = config.get('pattern')
        
        if len(value) < min_length:
            raise ValidationError(
                _('حداقل {} کاراکتر وارد کنید').format(min_length),
                code='min_length'
            )
        
        if len(value) > max_length:
            raise ValidationError(
                _('حداکثر {} کاراکتر مجاز است').format(max_length),
                code='max_length'
            )
        
        if pattern:
            if not re.match(pattern, value):
                raise ValidationError(
                    _('فرمت وارد شده صحیح نیست'),
                    code='pattern'
                )
        
        return value
    
    def _validate_textarea(self, value: Any, config: dict) -> str:
        """Validate textarea field."""
        value = str(value)
        max_length = config.get('max_length', 5000)
        
        if len(value) > max_length:
            raise ValidationError(
                _('حداکثر {} کاراکتر مجاز است').format(max_length),
                code='max_length'
            )
        
        return value
    
    def _validate_number(self, value: Any, config: dict) -> int:
        """Validate integer number field."""
        try:
            value = int(value)
        except (ValueError, TypeError):
            raise ValidationError(
                _('عدد صحیح وارد کنید'),
                code='invalid_number'
            )
        
        min_val = config.get('min')
        max_val = config.get('max')
        
        if min_val is not None and value < min_val:
            raise ValidationError(
                _('مقدار نمی‌تواند کمتر از {} باشد').format(min_val),
                code='min_value'
            )
        
        if max_val is not None and value > max_val:
            raise ValidationError(
                _('مقدار نمی‌تواند بیشتر از {} باشد').format(max_val),
                code='max_value'
            )
        
        return value
    
    def _validate_decimal(self, value: Any, config: dict) -> str:
        """Validate decimal number field. Returns string for JSON serialization."""
        try:
            decimal_value = Decimal(str(value))
        except (InvalidOperation, ValueError):
            raise ValidationError(
                _('عدد اعشاری معتبر وارد کنید'),
                code='invalid_decimal'
            )
        
        precision = config.get('precision', 2)
        min_val = config.get('min')
        max_val = config.get('max')
        
        if min_val is not None and decimal_value < Decimal(str(min_val)):
            raise ValidationError(
                _('مقدار نمی‌تواند کمتر از {} باشد').format(min_val),
                code='min_value'
            )
        
        if max_val is not None and decimal_value > Decimal(str(max_val)):
            raise ValidationError(
                _('مقدار نمی‌تواند بیشتر از {} باشد').format(max_val),
                code='max_value'
            )
        
        # Round to specified precision
        return str(round(decimal_value, precision))
    
    def _validate_date(self, value: Any, config: dict) -> str:
        """Validate date field. Returns ISO format string."""
        if isinstance(value, date):
            date_val = value
        elif isinstance(value, str):
            try:
                date_val = datetime.fromisoformat(value.replace('Z', '+00:00')).date()
            except ValueError:
                raise ValidationError(
                    _('تاریخ معتبر وارد کنید'),
                    code='invalid_date'
                )
        else:
            raise ValidationError(
                _('فرمت تاریخ نامعتبر است'),
                code='invalid_format'
            )
        
        min_date = config.get('min_date')
        max_date = config.get('max_date')
        
        if min_date:
            min_date_val = datetime.fromisoformat(min_date).date() if isinstance(min_date, str) else min_date
            if date_val < min_date_val:
                raise ValidationError(
                    _('تاریخ نمی‌تواند قبل از {} باشد').format(min_date),
                    code='min_date'
                )
        
        if max_date:
            max_date_val = datetime.fromisoformat(max_date).date() if isinstance(max_date, str) else max_date
            if date_val > max_date_val:
                raise ValidationError(
                    _('تاریخ نمی‌تواند بعد از {} باشد').format(max_date),
                    code='max_date'
                )
        
        return date_val.isoformat()
    
    def _validate_datetime(self, value: Any, config: dict) -> str:
        """Validate datetime field. Returns ISO format string."""
        if isinstance(value, datetime):
            dt_val = value
        elif isinstance(value, str):
            try:
                dt_val = datetime.fromisoformat(value.replace('Z', '+00:00'))
            except ValueError:
                raise ValidationError(
                    _('تاریخ و زمان معتبر وارد کنید'),
                    code='invalid_datetime'
                )
        else:
            raise ValidationError(
                _('فرمت تاریخ و زمان نامعتبر است'),
                code='invalid_format'
            )
        
        return dt_val.isoformat()
    
    def _validate_boolean(self, value: Any, config: dict) -> bool:
        """Validate boolean field."""
        if isinstance(value, bool):
            return value
        
        if isinstance(value, str):
            if value.lower() in ('true', '1', 'yes', 'بله'):
                return True
            if value.lower() in ('false', '0', 'no', 'خیر'):
                return False
        
        if isinstance(value, int):
            return bool(value)
        
        raise ValidationError(
            _('مقدار بله/خیر معتبر وارد کنید'),
            code='invalid_boolean'
        )
    
    def _validate_select(self, value: Any, config: dict) -> str:
        """Validate single select field."""
        options = config.get('options', [])
        valid_values = {opt['value'] if isinstance(opt, dict) else opt for opt in options}
        
        if value not in valid_values:
            raise ValidationError(
                _('گزینه انتخاب شده معتبر نیست'),
                code='invalid_choice'
            )
        
        return value
    
    def _validate_multiselect(self, value: Any, config: dict) -> list:
        """Validate multi-select field."""
        if not isinstance(value, list):
            if isinstance(value, str):
                value = [value]
            else:
                raise ValidationError(
                    _('لیست انتخاب‌ها معتبر نیست'),
                    code='invalid_multiselect'
                )
        
        options = config.get('options', [])
        valid_values = {opt['value'] if isinstance(opt, dict) else opt for opt in options}
        
        for item in value:
            if item not in valid_values:
                raise ValidationError(
                    _('گزینه {} معتبر نیست').format(item),
                    code='invalid_choice'
                )
        
        max_selections = config.get('max_selections')
        if max_selections and len(value) > max_selections:
            raise ValidationError(
                _('حداکثر {} گزینه قابل انتخاب است').format(max_selections),
                code='max_selections'
            )
        
        return value
    
    def _validate_email(self, value: Any, config: dict) -> str:
        """Validate email field."""
        value = str(value).strip().lower()
        
        try:
            validate_email(value)
        except Exception:
            raise ValidationError(
                _('ایمیل معتبر وارد کنید'),
                code='invalid_email'
            )
        
        return value
    
    def _validate_url(self, value: Any, config: dict) -> str:
        """Validate URL field."""
        value = str(value).strip()
        
        try:
            URLValidator()(value)
        except Exception:
            raise ValidationError(
                _('آدرس وب معتبر وارد کنید'),
                code='invalid_url'
            )
        
        return value
    
    def _validate_phone(self, value: Any, config: dict) -> str:
        """Validate phone number field."""
        value = str(value).strip()
        # Remove common formatting characters
        cleaned = re.sub(r'[\s\-\(\)]', '', value)
        
        # Basic phone validation (Iran format + international)
        if not re.match(r'^(\+98|0)?9\d{9}$|^\+?\d{7,15}$', cleaned):
            raise ValidationError(
                _('شماره تلفن معتبر وارد کنید'),
                code='invalid_phone'
            )
        
        return cleaned
    
    def _validate_json(self, value: Any, config: dict) -> dict | list:
        """Validate JSON field."""
        if isinstance(value, (dict, list)):
            return value
        
        if isinstance(value, str):
            import json
            try:
                return json.loads(value)
            except json.JSONDecodeError:
                raise ValidationError(
                    _('داده JSON معتبر وارد کنید'),
                    code='invalid_json'
                )
        
        raise ValidationError(
            _('فرمت داده JSON نامعتبر است'),
            code='invalid_json_format'
        )
    
    def _validate_file(self, value: Any, config: dict) -> dict:
        """
        Validate file field.
        
        Value should be a dict with: {id, name, url, size, type}
        or just the file ID if already uploaded.
        """
        if isinstance(value, str):
            # Assume it's a file ID
            return {'id': value}
        
        if isinstance(value, dict):
            required_keys = {'id'}
            if not required_keys.issubset(value.keys()):
                raise ValidationError(
                    _('اطلاعات فایل ناقص است'),
                    code='invalid_file_info'
                )
            return value
        
        raise ValidationError(
            _('فرمت فایل نامعتبر است'),
            code='invalid_file'
        )
    
    def _validate_image(self, value: Any, config: dict) -> dict:
        """Validate image field. Same as file but may include dimensions."""
        return self._validate_file(value, config)
    
    def _validate_relation(self, value: Any, config: dict) -> str | list:
        """
        Validate relation field.
        
        Value should be UUID(s) of related record(s).
        """
        multiple = config.get('multiple', False)
        
        if multiple:
            if not isinstance(value, list):
                value = [value]
            # Validate each ID is a valid UUID format
            for item in value:
                if not self._is_valid_uuid(item):
                    raise ValidationError(
                        _('شناسه رکورد مرتبط نامعتبر است'),
                        code='invalid_relation_id'
                    )
            return value
        else:
            if isinstance(value, list):
                value = value[0] if value else None
            if value and not self._is_valid_uuid(value):
                raise ValidationError(
                    _('شناسه رکورد مرتبط نامعتبر است'),
                    code='invalid_relation_id'
                )
            return value
    
    @staticmethod
    def _is_valid_uuid(val: Any) -> bool:
        """Check if value is a valid UUID."""
        import uuid
        try:
            uuid.UUID(str(val))
            return True
        except (ValueError, TypeError):
            return False


class MultiValidationError(Exception):
    """
    Container for multiple validation errors.
    """
    
    def __init__(self, errors: list[ValidationError]):
        self.errors = errors
        super().__init__(self._format_message())
    
    def _format_message(self) -> str:
        """Format all errors into a single message."""
        messages = [f"{e.field}: {e.message}" for e in self.errors]
        return "; ".join(messages)
    
    def to_dict(self) -> dict:
        """Convert to dict format for API responses."""
        return {
            'errors': [e.to_dict() for e in self.errors]
        }
