#
#
#

from octodns.processor.base import BaseProcessor


class Templating(BaseProcessor):
    '''
    Record templating using python format. For simple records like TXT and CAA
    that is the value itself. For multi-field records like MX or SRV it's the
    text portions, exchange and target respectively.

    Example Processor Config:

        templating:
          class: octodns.processor.templating.Templating
          # Any k/v present in context will be passed into the .format method and
          # thus be available as additional variables in the template. This is all
          # optional.
          context:
            key: value
            another: 42

    Example Records:

        foo:
          type: TXT
          value: The zone this record lives in is {zone_name}. There are {zone_num_records} record(s).

        bar:
          type: MX
          values:
            - preference: 1
              exchange: mx1.{zone_name}.mail.mx.
            - preference: 1
              exchange: mx2.{zone_name}.mail.mx.

    Note that validations for some types reject values with {}. When
    encountered the best option is to use record level `lenient: true`
    https://github.com/octodns/octodns/blob/main/docs/records.md#lenience

    Note that if you need to add dynamic context you can create a custom
    processor that inherits from Templating and passes them into the call to
    super, e.g.

        class MyTemplating(Templating):
            def __init__(self, *args, context={}, **kwargs):
                context['year'] = lambda desired, sources: datetime.now().strftime('%Y')
                super().__init__(*args, context, **kwargs)

    See https://docs.python.org/3/library/string.html#custom-string-formatting
    for details on formatting options. Anything possible in an `f-string` or
    `.format` should work here.

    '''

    def __init__(self, id, *args, context={}, **kwargs):
        super().__init__(id, *args, **kwargs)
        self.context = context

    def process_source_zone(self, desired, sources):
        sources = sources or []
        zone_params = {
            'zone_name': desired.decoded_name,
            'zone_decoded_name': desired.decoded_name,
            'zone_encoded_name': desired.name,
            'zone_num_records': len(desired.records),
            'zone_source_ids': ', '.join(s.id for s in sources),
            # add any extra context provided to us, if the value is a callable
            # object call it passing our params so that arbitrary dynamic
            # context can be added for use in formatting
            **{
                k: (v(desired, sources) if callable(v) else v)
                for k, v in self.context.items()
            },
        }

        def params(record):
            return {
                'record_name': record.decoded_name,
                'record_decoded_name': record.decoded_name,
                'record_encoded_name': record.name,
                'record_fqdn': record.decoded_fqdn,
                'record_decoded_fqdn': record.decoded_fqdn,
                'record_encoded_fqdn': record.fqdn,
                'record_type': record._type,
                'record_ttl': record.ttl,
                'record_source_id': record.source.id if record.source else None,
                **zone_params,
            }

        for record in desired.records:
            if hasattr(record, 'values'):
                if record.values and not hasattr(record.values[0], 'template'):
                    # the (custom) value type does not support templating
                    continue
                new_values = [v.template(params(record)) for v in record.values]
                if record.values != new_values:
                    new = record.copy()
                    new.values = new_values
                    desired.add_record(new, replace=True)
            else:
                if not hasattr(record.value, 'template'):
                    # the (custom) value type does not support templating
                    continue
                new_value = record.value.template(params(record))
                if record.value != new_value:
                    new = record.copy()
                    new.value = new_value
                    desired.add_record(new, replace=True)

        return desired
