"""Models and functions used for JSON schema."""
import json
from functools import partial
from string import Template
from typing import Any, Callable, Dict, List

from ywh2bt.core.schema.error import SchemaError
from ywh2bt.core.schema.json import root_configuration_as_json_schema

Json = Dict[str, Any]


def root_configuration_as_markdown() -> str:
    """
    Get a markdown schema for RootConfiguration.

    Raises:
        SchemaError: if an error occurred during the creation of the schema

    Returns:
        the schema
    """
    json_schema_str = root_configuration_as_json_schema()
    try:
        schema: Json = json.loads(json_schema_str)
    except TypeError as load_error:
        raise SchemaError('JSON load error') from load_error
    return '\n'.join(
        _schema_to_markdown(
            schema=schema,
        ),
    )


def _schema_to_markdown(
    schema: Json,
) -> List[str]:
    ref = schema.get('$ref')
    if ref:
        return [
            Template('* **Reference**: [`${reference}`](#${reference})').substitute(
                reference=ref.replace('#/definitions/', ''),
            ),
        ]
    any_of = schema.get('anyOf')
    if any_of:
        return _extract_any_of(
            any_of=any_of,
        )
    output = _extract_attributes(
        schema=schema,
        attributes={
            'title': Template('* **Title**: ${value}'),
            'description': Template('* **Description**: ${value}'),
            'type': Template('* **Type**: `${value}`'),
            'const': Template('* **Required constant value**: `${value}`'),
            'default': Template('* **Default value**: `${value}`'),
        },
    )
    output.extend(
        _extract_properties(
            schema=schema,
        ),
    )
    output.extend(
        _extract_properties_patterns(
            schema=schema,
        ),
    )
    output.extend(
        _extract_definitions(
            schema=schema,
        ),
    )
    output.extend(
        _extract_items(
            schema=schema,
        ),
    )

    return output


def _extract_attributes(
    schema: Json,
    attributes: Dict[str, Template],
) -> List[str]:
    output = []
    for attribute, template in attributes.items():
        value = schema.get(attribute)
        if value is not None:
            output.append(
                template.substitute(
                    value=value,
                ),
            )
    return output


def _extract_properties(
    schema: Json,
) -> List[str]:
    required = schema.get('required', [])
    properties = []
    for property_name, property_schema in schema.get('properties', {}).items():
        properties.append(f'- `{property_name}`')
        properties.extend(
            _prefix_lines(
                '    ',
                _schema_to_markdown(
                    schema=property_schema,
                ),
            ),
        )
        if property_name in required:
            properties.append('    * **Required**: True')
    if not properties:
        return []
    return [
        '* **Properties**:',
        *_prefix_lines(
            '    ',
            properties,
        ),
    ]


def _extract_properties_patterns(
    schema: Json,
) -> List[str]:
    return _extract_list(
        schema=schema,
        attribute_name='patternProperties',
        block_label='* **Properties patterns**:',
        item_name_template=Template('- **Pattern**: `${item_name}`'),
    )


def _extract_definitions(
    schema: Json,
) -> List[str]:
    return _extract_list(
        schema=schema,
        attribute_name='definitions',
        block_label='* **Definitions**:',
        item_name_template=Template('- <a name="${item_name}">`${item_name}`</a>:'),
    )


def _extract_items(
    schema: Json,
) -> List[str]:
    items = schema.get('items')
    if not items:
        return []
    return [
        '* **Items**:',
        *_prefix_lines(
            '    ',
            _schema_to_markdown(
                schema=items,
            ),
        ),
    ]


def _extract_list(
    schema: Json,
    attribute_name: str,
    block_label: str,
    item_name_template: Template,
) -> List[str]:
    items = []
    for item_name, item_schema in schema.get(attribute_name, {}).items():
        items.append(item_name_template.substitute(item_name=item_name))
        items.extend(
            _prefix_lines(
                '    ',
                _schema_to_markdown(
                    schema=item_schema,
                ),
            ),
        )
    if not items:
        return []
    return [
        block_label,
        *_prefix_lines(
            '    ',
            items,
        ),
    ]


def _extract_any_of(
    any_of: List[Json],
) -> List[str]:
    output = [
        '* Any of:',
    ]
    for any_of_entry in any_of:
        output.extend(
            _prefix_lines(
                '    ',
                list(_schema_to_markdown(
                    schema=any_of_entry,
                )),
            ),
        )
    return output


def _prefix(
    prefix: str,
    string: str,
) -> str:
    return f'{prefix}{string}'


def _prefix_lines(
    prefix: str,
    lines: List[str],
) -> List[str]:
    return list(map(
        _prefixer(prefix),
        lines,
    ))


def _prefixer(
    prefix: str,
) -> Callable[[str], str]:
    return partial(
        _prefix,
        prefix,
    )
