""" {{ class_name }} dataclass. """

# pylint: disable=too-many-lines, too-many-locals, too-many-branches, too-many-statements, too-many-arguments, line-too-long, wildcard-import

{%- if avro_annotation or dataclasses_json_annotation %}
import io
import gzip
{%- endif %}
import enum
import typing
import dataclasses
from dataclasses import dataclass
{%- if is_abstract %}
from abc import ABC
{%- endif %}
{%- if dataclasses_json_annotation %}
import dataclasses_json
from dataclasses_json import Undefined, dataclass_json
{%- for field in fields if field.type == "datetime.datetime" or field.type == "typing.Optional[datetime.datetime]" %}
{%- if loop.first %}
from marshmallow import fields
{%- endif %}
{%- endfor %}
{%- endif %}
{%- if avro_annotation or dataclasses_json_annotation %}
import json
{%- endif %}
{%- if avro_annotation %}
import avro.schema
import avro.io
{%- endif %}
{%- for import_type in import_types if import_type not in ['datetime.datetime', 'datetime.date', 'datetime.time', 'datetime.timedelta', 'decimal.Decimal', 'uuid.UUID'] %}
from {{ '.'.join(import_type.split('.')[:-1]) | lower }} import {{ import_type.split('.')[-1] }}
{%- endfor %}
{%- for import_type in import_types if import_type in ['datetime.datetime', 'datetime.date', 'datetime.time', 'datetime.timedelta'] %}
{%- if loop.first %}
import datetime
{%- endif %}
{%- endfor %}
{%- for import_type in import_types if import_type == 'decimal.Decimal' %}
{%- if loop.first %}
import decimal
{%- endif %}
{%- endfor %}
{%- for import_type in import_types if import_type == 'uuid.UUID' %}
{%- if loop.first %}
import uuid
{%- endif %}
{%- endfor %}

{% if dataclasses_json_annotation %}
@dataclass_json(undefined=Undefined.EXCLUDE)
{%- endif %}
@dataclass
class {{ class_name }}{% if base_class or is_abstract %}({% if base_class %}{{ base_class.split('.')[-1] }}{% if is_abstract %}, {% endif %}{% endif %}{% if is_abstract %}ABC{% endif %}){% endif %}:
    """
    {{ docstring }}
    {% if fields %}
    Attributes:
        {%- for field in fields %}
        {{ field.docstring }}
        {%- endfor -%}
    {% endif %}
    """
    {% if avro_annotation %}
    AvroType: typing.ClassVar[avro.schema.Schema] = avro.schema.parse(
        "{{ avro_schema_json }}"
    )
    {% endif %}
    {% for field in fields %}
    {%- set isdate = field.type == "datetime.datetime" or field.type == "typing.Optional[datetime.datetime]" %}
    {{ field.name }}: {{ field.type }}=dataclasses.field(kw_only=True{% if dataclasses_json_annotation %}, metadata=dataclasses_json.config(field_name="{{ field.original_name }}"{%- if isdate -%}, encoder=lambda d: datetime.datetime.isoformat(d) if d else None, decoder=lambda d:datetime.datetime.fromisoformat(d) if d else None, mm_field=fields.DateTime(format='iso'){%- endif -%}){%- endif %})
    {%- endfor %}

    @classmethod
    def from_serializer_dict(cls, data: dict) -> '{{ class_name }}':
        """
        Converts a dictionary to a dataclass instance.
        
        Args:
            data: The dictionary to convert to a dataclass.
        
        Returns:
            The dataclass representation of the dataclass.
        """
        {%- for field in fields %}
        {%- if field.name != field.original_name %}
        if '{{ field.original_name }}' in data:
            data['{{ field.name }}'] = data.pop('{{ field.original_name }}')
        {%- endif %}
        {%- endfor %}
        return cls(**data)

    {%- if avro_annotation %}
    @classmethod
    def from_avro_dict(cls, data: dict) -> '{{ class_name }}':
        """
        Converts a dictionary from Avro deserialization to a dataclass instance.
        Handles conversion of string representations back to Python types for
        extended logical types.
        
        Args:
            data: The dictionary from Avro deserialization.
        
        Returns:
            The dataclass representation.
        """
        # Convert string values back to Python types for Avro string-based logical types
        converted = data.copy()
        
        {%- for field in fields %}
        {%- set field_key = field.original_name if field.name != field.original_name else field.name %}
        if '{{ field_key }}' in converted and converted['{{ field_key }}'] is not None:
            value = converted['{{ field_key }}']
            {%- if field.source_type in ['date', 'datetime', 'timestamp', 'time', 'duration', 'decimal', 'uuid', 'int128', 'uint128'] %}
            {%- if field.source_type == 'date' %}
            if isinstance(value, str):
                converted['{{ field_key }}'] = datetime.date.fromisoformat(value)
            {%- elif field.source_type in ['datetime', 'timestamp'] %}
            if isinstance(value, str):
                converted['{{ field_key }}'] = datetime.datetime.fromisoformat(value)
            {%- elif field.source_type == 'time' %}
            if isinstance(value, str):
                converted['{{ field_key }}'] = datetime.time.fromisoformat(value)
            {%- elif field.source_type == 'duration' %}
            if isinstance(value, str):
                converted['{{ field_key }}'] = datetime.timedelta(seconds=float(value))
            {%- elif field.source_type == 'decimal' %}
            if isinstance(value, str):
                converted['{{ field_key }}'] = decimal.Decimal(value)
            {%- elif field.source_type == 'uuid' %}
            if isinstance(value, str):
                converted['{{ field_key }}'] = uuid.UUID(value)
            {%- elif field.source_type in ['int128', 'uint128'] %}
            if isinstance(value, str):
                converted['{{ field_key }}'] = int(value)
            {%- endif %}
            {%- endif %}
        {%- if field.name != field.original_name %}
        if '{{ field.original_name }}' in converted:
            converted['{{ field.name }}'] = converted.pop('{{ field.original_name }}')
        {%- endif %}
        {%- endfor %}
        
        return cls(**converted)
    {%- endif %}

    def to_serializer_dict(self) -> dict:
        """
        Converts the dataclass to a dictionary.

        Returns:
            The dictionary representation of the dataclass.
        """
        asdict_result = dataclasses.asdict(self, dict_factory=self._dict_resolver)
        {%- for field in fields %}
        {%- if field.name != field.original_name and field.original_name+'_' != field.name %}
        if '{{ field.name }}' in asdict_result:
            asdict_result['{{ field.original_name }}'] = asdict_result.pop('{{ field.name }}')
        {%- endif %}
        {%- endfor %}
        return asdict_result

    def _dict_resolver(self, data):
        """
        Helps resolving the Enum values to their actual values and fixes the key names.
        """ 
        def _resolve_enum(v):
            if isinstance(v, enum.Enum):
                return v.value
            return v
        def _fix_key(k):
            return k[:-1] if k.endswith('_') else k
        return {_fix_key(k): _resolve_enum(v) for k, v in iter(data)}
    {%- if avro_annotation %}

    def to_avro_dict(self) -> dict:
        """
        Converts the dataclass to a dictionary suitable for Avro serialization.
        Handles conversion of Python types to Avro-compatible string representations
        for extended logical types.

        Returns:
            The dictionary representation suitable for Avro serialization.
        """
        result = self.to_serializer_dict()
        converted = result.copy()
        
        # Convert specific fields based on their source types
        {%- for field in fields %}
        {%- set field_key = field.original_name if field.name != field.original_name else field.name %}
        {%- if field.source_type in ['date', 'datetime', 'timestamp', 'time', 'duration', 'decimal', 'uuid', 'int128', 'uint128'] %}
        if '{{ field_key }}' in converted and converted['{{ field_key }}'] is not None:
            value = converted['{{ field_key }}']
            {%- if field.source_type == 'date' %}
            if isinstance(value, datetime.date):
                converted['{{ field_key }}'] = value.isoformat()
            {%- elif field.source_type in ['datetime', 'timestamp'] %}
            if isinstance(value, datetime.datetime):
                converted['{{ field_key }}'] = value.isoformat()
            {%- elif field.source_type == 'time' %}
            if isinstance(value, datetime.time):
                converted['{{ field_key }}'] = value.isoformat()
            {%- elif field.source_type == 'duration' %}
            if isinstance(value, datetime.timedelta):
                converted['{{ field_key }}'] = str(value.total_seconds())
            {%- elif field.source_type == 'decimal' %}
            if isinstance(value, decimal.Decimal):
                converted['{{ field_key }}'] = str(value)
            {%- elif field.source_type == 'uuid' %}
            if isinstance(value, uuid.UUID):
                converted['{{ field_key }}'] = str(value)
            {%- elif field.source_type in ['int128', 'uint128'] %}
            if isinstance(value, int):
                converted['{{ field_key }}'] = str(value)
            {%- endif %}
        {%- endif %}
        {%- endfor %}
        
        return converted
    {%- endif %}
    {%- if avro_annotation or dataclasses_json_annotation %}

    def to_byte_array(self, content_type_string: str) -> bytes:
        """
        Converts the dataclass to a byte array based on the content type string.
        
        Args:
            content_type_string: The content type string to convert the dataclass to.
                Supported content types:
                    {%- if dataclasses_json_annotation %}
                    'application/json': Encodes the data to JSON format.
                    {%- endif %}
                    {%- if avro_annotation %}
                    'avro/binary': Encodes the data to Avro binary format.
                    'application/vnd.apache.avro+avro': Encodes the data to Avro binary format.
                    {%- endif %}
                Supported content type extensions:
                    '+gzip': Compresses the byte array using gzip, e.g. 'application/json+gzip'.

        Returns:
            The byte array representation of the dataclass.        
        """
        content_type = content_type_string.split(';')[0].strip()
        result = None
        
        # Strip compression suffix for base type matching
        base_content_type = content_type.replace('+gzip', '')

        {%- if avro_annotation %}
        if base_content_type in ['avro/binary', 'application/vnd.apache.avro+avro']:
            # Convert to Avro binary format using the embedded schema
            writer = avro.io.DatumWriter(self.AvroType)
            with io.BytesIO() as stream:
                encoder = avro.io.BinaryEncoder(stream)
                writer.write(self.to_avro_dict(), encoder)
                result = stream.getvalue()
        {%- endif %}
        {%- if dataclasses_json_annotation %}
        if base_content_type == 'application/json':
            #pylint: disable=no-member
            result = self.to_json()
            #pylint: enable=no-member
        {%- endif %}

        if result is not None and content_type.endswith('+gzip'):
            # Handle string result from to_json()
            if isinstance(result, str):
                result = result.encode('utf-8')
            with io.BytesIO() as stream:
                with gzip.GzipFile(fileobj=stream, mode='wb') as gzip_file:
                    gzip_file.write(result)
                result = stream.getvalue()

        if result is None:
            raise NotImplementedError(f"Unsupported media type {content_type}")

        return result

    @classmethod
    def from_data(cls, data: typing.Any, content_type_string: typing.Optional[str] = None) -> typing.Optional['{{ class_name }}']:
        """
        Converts the data to a dataclass based on the content type string.
        
        Args:
            data: The data to convert to a dataclass.
            content_type_string: The content type string to convert the data to. 
                Supported content types:
                    {%- if dataclasses_json_annotation %}
                    'application/json': Attempts to decode the data from JSON encoded format.
                    {%- endif %}
                    {%- if avro_annotation %}
                    'avro/binary': Attempts to decode the data from Avro binary format.
                    'application/vnd.apache.avro+avro': Attempts to decode the data from Avro binary format.
                    {%- endif %}
                Supported content type extensions:
                    '+gzip': First decompresses the data using gzip, e.g. 'application/json+gzip'.
        Returns:
            The dataclass representation of the data.
        """
        if data is None:
            return None
        if isinstance(data, cls):
            return data
        if isinstance(data, dict):
            return cls.from_serializer_dict(data)

        content_type = (content_type_string or 'application/octet-stream').split(';')[0].strip()

        if content_type.endswith('+gzip'):
            if isinstance(data, (bytes, io.BytesIO)):
                stream = io.BytesIO(data) if isinstance(data, bytes) else data
            else:
                raise NotImplementedError('Data is not of a supported type for gzip decompression')
            with gzip.GzipFile(fileobj=stream, mode='rb') as gzip_file:
                data = gzip_file.read()
        
        # Strip compression suffix for base type matching
        base_content_type = content_type.replace('+gzip', '')

        {%- if avro_annotation %}
        if base_content_type in ['avro/binary', 'application/vnd.apache.avro+avro']:
            if isinstance(data, bytes):
                # Decode from Avro binary format using the embedded schema
                reader = avro.io.DatumReader(cls.AvroType)
                with io.BytesIO(data) as stream:
                    decoder = avro.io.BinaryDecoder(stream)
                    _record = reader.read(decoder)
                    return {{ class_name }}.from_avro_dict(_record)
            else:
                raise NotImplementedError('Data is not of a supported type for Avro deserialization')
        {%- endif %}
        {%- if dataclasses_json_annotation %}
        if base_content_type == 'application/json':
            if isinstance(data, (bytes, str)):
                data_str = data.decode('utf-8') if isinstance(data, bytes) else data
                _record = json.loads(data_str)
                {%- for field in fields %}
                {%- if field.name != field.original_name %}
                if '{{ field.original_name }}' in _record:
                    _record['{{ field.name }}'] = _record.pop('{{ field.original_name }}')
                {%- endif %}
                {%- endfor %}
                return {{ class_name }}.from_serializer_dict(_record)
            else:
                raise NotImplementedError('Data is not of a supported type for JSON deserialization')
        {%- endif %}
        raise NotImplementedError(f'Unsupported media type {content_type}')
    {%- endif %}

    @classmethod
    def create_instance(cls) -> '{{ class_name }}':
        """
        Creates an instance of the dataclass with test values.
        
        Returns:
            An instance of the dataclass.
        """
        return cls(
            {%- for field in fields %}
            {{ field.name }}={{ field.test_value }}{{ "," if not loop.last else "" }}
            {%- endfor %}
        )
