import pathlib
from typing import Optional
from jinja2 import Template
from ..json_schema import JsonSchema
from ..json_schema_parser import JsonSchemaParser

PYTHON_ENUM = Template('''

class {{ class_name }}(Enum):
{%- for prop in props %}
    {{ prop }}
{%- endfor %}

''')

PYTHON_CLASS = Template('''

class {{ class_name }}Required(TypedDict):
{%- if required_props %}
{%- for prop in required_props %}
    # {{ prop[0] }}
    {{ prop[1] }}
{%- endfor %}
{%- else %}
    pass
{%- endif %}


class {{ class_name }}Optional(TypedDict, total=False):
{%- if optional_props %}
{%- for prop in optional_props %}
    # {{ prop[0] }}
    {{ prop[1] }}
{%- endfor %}
{%- else %}
    pass
{%- endif %}


class {{ class_name }}({{ class_name }}Required, {{ class_name }}Optional):
    pass

''')


def js_to_pythontype(name: str, js: JsonSchema, parent: JsonSchema) -> str:
    '''
    convert JsonSchema to pythontype

    * Extension: ToDo
    * Extra: ToDo

    * object with additionalProperties => Dict[str, T]
    * array => List[T]
    * else => Optional[T]
    '''
    if name in ['extensions', 'extras']:
        return 'dict'

    enum_values = js.enumerate_values()
    if enum_values:
        return js.title

    if js.type == 'integer':
        return 'int'
    if js.type == 'number':
        return 'float'
    elif js.type == 'boolean':
        return 'bool'
    elif js.type == 'string':
        return 'str'
    elif js.type == 'array':
        return f'List[{js_to_pythontype(name, js.items, js)}]'
    # elif js.type == 'unknown':
    #     return 'Dict[str, Any]'
    else:  # js.type == 'object' or else
        # if js.properties and js.additionalProperties:
        #     raise Exception()
        if js.properties:
            return js.title
        elif js.additionalProperties:
            return f'Dict[str, {js_to_pythontype(name, js.additionalProperties, js)}]'
        else:
            return 'Dict[str, Any]'


def add_optional(src: str, required: bool) -> str:
    if required:
        return f'{src}'

    # if src.startswith('Dict['):
    #     return f'{src}'
    # elif src.startswith('List['):
    #     return f'{src}'
    # else:
    return f'Optional[{src}] = None'


def read_func(name: str, js: JsonSchema, parent: Optional[JsonSchema]) -> str:
    '''
    from_dict
    '''
    if js.enumerate_values():
        return f'if "{name}" in src: dst["{name}"] = {js.title}(src["{name}"]) # noqa'

    if js.properties:
        # object
        return f'if "{name}" in src: dst["{name}"] = {js.title}.from_dict(src["{name}"]) # noqa'

    if js.additionalProperties or js.type == 'unknown':
        return f'dst["{name}"] = src.get("{name}", {{}})'

    if js.type == 'array':
        if js.items.properties:
            return f'dst["{name}"] = [{js.items.title}.from_dict(item) for item in src["{name}"]] if "{name}" in src else [] # noqa'
        else:
            return f'dst["{name}"] = src.get("{name}", [])'

    # return f'# {name} do nothing'
    return f'if "{name}" in src: dst["{name}"] = src["{name}"] # noqa copy'


def write_func(name: str, js: JsonSchema, parent: Optional[JsonSchema]) -> str:
    '''
    to_dict
    '''
    condition = f'if is_enable(self.{name}): '

    if js.properties or js.enumerate_values():
        return f'{condition}d["{name}"] = self.{name}.to_dict() # noqa'

    if js.type == 'array':
        condition = f'if self.{name}: '
        if js.items.properties:
            return f'{condition}d["{name}"] = [item.to_dict() for item in self.{name}] # noqa'

    return f'{condition}d["{name}"] = self.{name} # noqa'


def escape_enum(src: str) -> str:
    splitted = src.split('/')
    if len(splitted) > 1:
        # ex. image/jpg
        return ''.join(x.title() for x in splitted)
    else:
        return src


def enum_value(src: str) -> str:
    splitted = src.split('=')
    if len(splitted) == 2:
        # int
        return f'{escape_enum(splitted[0])} = {splitted[1]}'
    else:
        # str
        return f'{escape_enum(splitted[0])} = "{splitted[0]}"'


def generate(parser: JsonSchemaParser, dst: pathlib.Path) -> None:
    if not parser.root:
        return

    print(f'write: {dst}')
    dst.parent.mkdir(parents=True, exist_ok=True)
    with dst.open('w') as w:

        w.write('''# this is generated by sukonbu
from typing import TypedDict, List, Dict
from enum import Enum
''')

        def enumerate_schema(_js: JsonSchema):
            if _js.properties:
                for k, v in _js.properties.items():
                    # print(k)
                    for x in enumerate_schema(v):
                        yield x
                yield _js

            elif _js.items:
                for x in enumerate_schema(_js.items):
                    yield x
                yield _js

            elif _js.additionalProperties:
                for x in enumerate_schema(_js.additionalProperties):
                    yield x

            elif _js.enumerate_values():
                yield _js

        used = {}

        for js in enumerate_schema(parser.root):
            # for key, js, parent in parser.schemas:
            enum_values = js.enumerate_values()
            if enum_values:
                # if not parent:
                #     raise Exception()
                value_map = {
                    'class_name':
                    js.title,
                    'props': [enum_value(value) for value in enum_values],
                    'reads':
                    [read_func(k, v, js) for k, v in js.properties.items()]
                }
                w.write(PYTHON_ENUM.render(**value_map))

            elif js.properties:
                if js.title in used:
                    continue
                used[js.title] = True

                required = []
                optional = []

                for k, v in js.properties.items():
                    python_type = js_to_pythontype(k, v, js)
                    desc = v.description
                    if v.default:
                        desc += f'\n    # default={v.default}'
                    if k in js.required:
                        required.append((desc, f'{k}: {python_type}'))
                    else:
                        optional.append((desc, f'{k}: {python_type}'))

                value_map = {
                    'class_name':
                    js.title,
                    'required_props': required,
                    'optional_props': optional,
                    'writes':
                    [write_func(k, v, js) for k, v in js.properties.items()],
                    'reads':
                    [read_func(k, v, js) for k, v in js.properties.items()]
                }
                w.write(PYTHON_CLASS.render(**value_map))

        w.write('''

if __name__ == '__main__':
    pass
''')
