# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from yapconf.items import YapconfDictItem

# flake8: noqa
HEADER = """# {app_name} Configuration

This document describes the configuration for {app_name}. Each section will 
document a particular configuration value and its description. First, 
though, we start with the possible sources. 

This documentation was auto-generated by [yapconf.](https://github.com/loganasherjones/yapconf)
"""

CONFIG_HEADER = """## Configuration

This section outlines the various configuration items {app_name} supports.
"""

SOURCES_HEADER = """
## Sources

{app_name} configuration can be loaded from the below sources:
"""

SOURCE_TEMPLATE = """
#### {source_label}

{source_label} is of type {source_type_link}.

{source_type_description}
"""

ITEM_TEMPLATE = """### {name}

{description}

{attribute_table}
{item_options}

{long_description}
"""

SOURCE_TYPE_LINKS = {
    'dict': 'https://yapconf.readthedocs.io/en/stable/sources.html#dict',
    'environment': 'https://yapconf.readthedocs.io/en/stable/sources.html#environment',
    'etcd': 'https://yapconf.readthedocs.io/en/stable/sources.html#etcd',
    'json': 'https://yapconf.readthedocs.io/en/stable/sources.html#json',
    'kubernetes': 'https://yapconf.readthedocs.io/en/stable/sources.html#kubernetes',
    'yaml': 'https://yapconf.readthedocs.io/en/stable/sources.html#yaml',
}

VALID_JSON_LINK = "[valid JSON](https://www.json.org/)"
VALID_YAML_LINK = "[valid YAML](http://yaml.org/)"

DICT_DESCRIPTION = """
The dict source means that {app_name} will load configuration values from an 
in-memory dictionary that cannot be modified externally. Usually, it is used as 
a list of sensible defaults."""

ENVIRONMENT_DESCRIPTION = """
This environment source means that {app_name} will load configuration values 
by using their `env_name` that is documented below."""

ETCD_DESCRIPTION = """
The etcd source means that {app_name} will load configuration values from the 
`{key}` in etcd."""

JSON_DATA_DESCRIPTION = """
This json source means that {app_name} will load configuration values from an 
in-memory string and cannot be modified. Usually it is used as a list of 
sensible defaults."""

JSON_FILE_DESCRIPTION = """
This json source means that {app_name} will load configuration values from 
`{filename}`. You may edit this file with {valid_link}."""

K8S_DESCRIPTION = """The kubernetes source indicates that the configuration 
may be loaded from a kubernetes [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/). 
Specifically, {app_name} will load the configuration from the ConfigMap named 
`{config_map_name}` in the namespace `{config_map_namespace}`"""

K8S_KEY_DESCRIPTION = """
Additionally, {source_label} specified that `{key}` exists in the 
`{config_map_name}` ConfigMap. `{key}` will be loaded as `{config_type}`. 
You may modify this entry with {valid_link}."""

YAML_DESCRIPTION = """
The yaml source means that {app_name} will load configuration values from 
`{filename}`. You may edit this file with {valid_link}"""


def _find_row_maxes(headers, rows):
    maxes = {}
    for key, value in headers.items():
        maxes[key] = len(value)
    for row in rows:
        for key, value in row.items():
            maxes[key] = max(maxes[key], len(value))
    return maxes


def _get_padding(key, row_maxes, value, pad_value=" ", modifier=0):
    return pad_value * (row_maxes[key] - len(value) + modifier)


def _build_row(row, row_maxes, row_keys, pad_value=" "):
    row_value = "|"
    for key in row_keys:
        padding = _get_padding(key, row_maxes, row[key], pad_value)
        row_value = row_value + ' ' + row[key] + padding + ' |'
    return row_value


def _build_separator(row_maxes, row_keys):
    fake_row = row_maxes.copy()
    for key, value in row_maxes.items():
        fake_row[key] = ''
    return _build_row(fake_row, row_maxes, row_keys, pad_value='-')


def build_markdown_table(headers, rows, row_keys=None):
    """Build a lined up markdown table.

    Args:
        headers (dict): A key -> value pairing fo the headers.
        rows (list): List of dictionaries that contain all the keys listed in
        the headers.
        row_keys (list): A sorted list of keys to display

    Returns:
        A valid Markdown Table as a string.
    """
    row_maxes = _find_row_maxes(headers, rows)
    row_keys = row_keys or [key for key, value in headers.items()]
    table = [
        _build_row(headers, row_maxes, row_keys),
        _build_separator(row_maxes, row_keys)
    ]

    for row in rows:
        table.append(_build_row(row, row_maxes, row_keys))
    return '\n'.join(table) + '\n'


def _generate_source_description(source, app_name, source_label):
    format_kwargs = {'app_name': app_name}
    template = ""
    if source.type == 'dict':
        template = DICT_DESCRIPTION

    elif source.type == 'environment':
        template = ENVIRONMENT_DESCRIPTION

    elif source.type == 'etcd':
        template = ETCD_DESCRIPTION
        format_kwargs['key'] = source.key

    elif source.type == 'json':
        if source.filename:
            template = JSON_FILE_DESCRIPTION
            format_kwargs['filename'] = source.filename
            format_kwargs['valid_link'] = VALID_JSON_LINK
        else:
            template = JSON_DATA_DESCRIPTION

    elif source.type == 'kubernetes':
        template = K8S_DESCRIPTION
        format_kwargs['config_map_name'] = source.name
        format_kwargs['config_map_namespace'] = source.namespace

        if source.key:
            template = "\n".join([t for t in [template, K8S_KEY_DESCRIPTION]])
            format_kwargs['source_label'] = source_label
            format_kwargs['key'] = source.key
            format_kwargs['config_type'] = source.config_type
            if source.config_type == 'yaml':
                format_kwargs['valid_link'] = VALID_YAML_LINK
            else:
                format_kwargs['valid_link'] = VALID_JSON_LINK

    elif source.type == 'yaml':
        template = YAML_DESCRIPTION
        format_kwargs['filename'] = source.filename
        format_kwargs['valid_link'] = VALID_YAML_LINK

    return template.format(**format_kwargs)


def _generate_source_section(source_label, source, app_name):
    source_type_link = (
        "[%s](%s)" % (source.type, SOURCE_TYPE_LINKS[source.type])
    )

    source_type_description = _generate_source_description(
        source, app_name, source_label
    )
    return SOURCE_TEMPLATE.format(
        source_label=source_label,
        source_type_link=source_type_link,
        source_type_description=source_type_description,
    )


def _generate_item_table(item):
    headers = {
        'attribute': 'Attribute',
        'value': 'Value',
    }
    if item.cli_support:
        cli_names = item.cli_names
    else:
        cli_names = None

    rows = [
        {
            'attribute': '**item_type**',
            'value': '`%s`' % item.item_type,
        },
        {
            'attribute': '**default**',
            'value': '`%s`' % item.default,
        },
        {
            'attribute': '**env_name**',
            'value': '`%s`' % item.env_name,
        },
        {
            'attribute': '**required**',
            'value': '`%s`' % item.required,
        },
        {
            'attribute': '**cli_name**',
            'value': '`%s`' % cli_names,
        },
        {
            'attribute': '**fallback**',
            'value': '`%s`' % item.fallback
        },
        {
            'attribute': '**choices**',
            'value': '`%s`' % item.choices,
        },
    ]
    return build_markdown_table(headers, rows, ['attribute', 'value'])


def _generate_item_options(item, app_name):
    options = []
    if item.env_name:
        options.append(
            'You can set {name} from the environment by setting the '
            'environment variable `{env_name}`'.format(
                name=item.fq_name,
                env_name=item.env_name
            )
        )

    if item.cli_support:
        cli_names = item.cli_names
        options.append(
            'You can set `{name}` from the command-line by specifying '
            '`{cli_names}` at {app_name}\'s entrypoint.'.format(
                name=item.fq_name,
                cli_names=cli_names,
                app_name=app_name,
            )
        )

    if item.fallback:
        option = (
            'If `{name}` is not set in any of the sources listed, it will '
            'attempt to fallback to the value set in `{fallback}`.'
        ).format(
            name=item.fq_name,
            fallback=item.fallback
        )

        if item.default:
            option += (
                ' If neither the fallback nor the original key is found '
                'then the value will fallback to the default of `{default}`'
            ).format(default=('%s' % item.default))

        options.append(option)
    elif item.default:
        options.append((
            'If `{name}` is not set in any of the sources listed, it will '
            'fallback to the default value `{value}`'.format(
                name=item.fq_name,
                value=item.default
            )
        ))

    return '\n\n'.join(options)


def _generate_item_section(item, app_name):
    item_table = _generate_item_table(item)
    item_options = _generate_item_options(item, app_name)
    long_description = item.long_description or ""
    return ITEM_TEMPLATE.format(
        name=item.fq_name,
        description=item.description or "No description provided.",
        attribute_table=item_table,
        item_options=item_options,
        long_description=long_description,
    )


def _get_table_row_from_item(item):
    return {
        'name': '[' + item.fq_name + '](#' + item.fq_name + ')',
        'type': item.item_type,
        'default': '%s' % item.default,
        'description': '%s' % item.description,
    }


def _generate_item_sections(items, app_name):
    rows = []
    item_sections = []
    for item in items:
        if isinstance(item, YapconfDictItem):
            tmp_rows, tmp_sections = _generate_item_sections(
                _sorted_dict_values(item.children),
                app_name
            )
            rows += tmp_rows
            item_sections += tmp_sections
        else:
            rows.append(_get_table_row_from_item(item))
            item_sections.append(_generate_item_section(item, app_name))

    return rows, item_sections


def _sorted_dict_values(dictionary):
    sorted_keys = sorted(list(dictionary))
    return [dictionary[key] for key in sorted_keys]


def generate_markdown_doc(app_name, spec):
    """Generate Markdown Documentation for the given spec/app name.

    Args:
        app_name (str): The name of the application.
        spec (YapconfSpec): A yapconf specification with sources loaded.

    Returns (str):
        A valid, markdown string representation of the documentation for
        the given specification.
    """

    # Apply standard headers.
    sections = [
        HEADER.format(app_name=app_name),
        SOURCES_HEADER.format(app_name=app_name)
    ]

    # Generate the sources section of the documentation
    sorted_labels = sorted(list(spec.sources))
    for label in sorted_labels:
        sections.append(
            _generate_source_section(label, spec.sources[label], app_name)
        )

    # Generate the config section.
    sections.append(CONFIG_HEADER.format(app_name=app_name))
    table_rows, item_sections = _generate_item_sections(
        _sorted_dict_values(spec.items),
        app_name
    )

    headers = {
        'name': 'Name',
        'type': 'Type',
        'default': 'Default',
        'description': 'Description'
    }

    sections.append(
        build_markdown_table(
            headers,
            table_rows,
            ['name', 'type', 'default', 'description'],
        )
    )
    for item_section in item_sections:
        sections.append(item_section)

    return '\n'.join([section for section in sections])
