from datetime import datetime
import re
from marshmallow import Schema, fields, ValidationError
from typing import Dict, Any, Optional, Type, Union
from config import item_attributes
from definitions.attributes import *

def create_text_field(attrib: textAttribute) -> fields.String:
    """Create a Marshmallow String field for textAttribute."""
    def validate_allowed_chars(value: str, attrib=attrib) -> None:
        if attrib.allowed_chars and not all(char in attrib.allowed_chars for char in value.strip()):
            raise ValidationError(f"Invalid characters in {attrib.display_name}. Allowed characters are: {attrib.allowed_chars}")

    return fields.String(
        required=attrib.required,
        validate=[
            lambda x, attrib=attrib: len(x.strip()) <= attrib.max_length if attrib.max_length else True,
            lambda x, attrib=attrib: len(x.strip()) >= attrib.min_length if attrib.min_length else True,
            lambda x, attrib=attrib: re.match(attrib.regex, x) if attrib.regex else True,
            validate_allowed_chars,
        ],
        error_messages={
            "required": f"{attrib.display_name} is required.",
            "validator_failed": f"Invalid value for {attrib.display_name}.",
        },
    )

def create_numeric_field(attrib: Union[intAttribute, floatAttribute]) -> Union[fields.Integer, fields.Float]:
    """Create a Marshmallow Integer or Float field for intAttribute or floatAttribute."""
    field_type = fields.Integer if isinstance(attrib, intAttribute) else fields.Float
    return field_type(
        required=attrib.required,
        validate=[
            lambda x, attrib=attrib: x >= attrib.min_val if attrib.min_val is not None else True,
            lambda x, attrib=attrib: x <= attrib.max_val if attrib.max_val is not None else True,
        ],
        error_messages={
            "required": f"{attrib.display_name} is required.",
            "validator_failed": f"Invalid value for {attrib.display_name}.",
        },
    )

def create_date_field(attrib: dateAttribute) -> fields.Date:
    """Create a Marshmallow Date field for dateAttribute."""
    return fields.Date(
        required=attrib.required,
        format="%Y-%m-%d",
        validate=[
            lambda x, attrib=attrib: x >= datetime.strptime(attrib.min_val, "%Y-%m-%d").date()
            if attrib.min_val
            else True,
            lambda x, attrib=attrib: x <= datetime.strptime(attrib.max_val, "%Y-%m-%d").date()
            if attrib.max_val
            else True,
        ],
        error_messages={
            "required": f"{attrib.display_name} is required.",
            "validator_failed": f"Invalid value for {attrib.display_name}.",
        },
    )

def create_select_field(attrib: selectAttribute) -> fields.String:
    """Create a Marshmallow String field for selectAttribute."""
    return fields.String(
        required=attrib.required,
        validate=[lambda x, attrib=attrib: x in attrib.options],
        error_messages={
            "required": f"{attrib.display_name} is required.",
            "validator_failed": f"Invalid value for {attrib.display_name}. Must be one of: {', '.join(attrib.options)}.",
        },
    )

def create_dynamic_schema() -> Type[Schema]:
    """
    Dynamically creates a Marshmallow Schema based on the configuration in item_attributes.
    """
    schema_fields = {}  # Dictionary to store dynamically created fields

    for attrib in item_attributes:
        if isinstance(attrib, textAttribute):
            schema_fields[attrib.attrib_name] = create_text_field(attrib)
        elif isinstance(attrib, (intAttribute, floatAttribute)):
            schema_fields[attrib.attrib_name] = create_numeric_field(attrib)
        elif isinstance(attrib, dateAttribute):
            schema_fields[attrib.attrib_name] = create_date_field(attrib)
        elif isinstance(attrib, selectAttribute):
            schema_fields[attrib.attrib_name] = create_select_field(attrib)

    # Dynamically create the schema class
    DynamicSchema = type("DynamicSchema", (Schema,), schema_fields)
    return DynamicSchema

def validate_values(form_data: Dict[str, Any]) -> Optional[str]:
    """
    Validate form data against the configuration in item_attributes using Marshmallow.
    Returns an error message if invalid, otherwise None.
    """
    DynamicSchema = create_dynamic_schema()
    schema = DynamicSchema()

    try:
        schema.load(form_data)  # Validate the data
        return None  # No errors
    except ValidationError as e:
        # Format the error message for display
        error_messages = []
        for field, errors in e.messages.items():
            for error in errors:
                error_messages.append(f"{field}: {error}")
        return "; ".join(error_messages)