"""
HTML form generator
"""
from typing import TYPE_CHECKING
from urllib.parse import parse_qs

from ixoncdkingress.function.api_client import ApiClient
from ixoncdkingress.function.context import FunctionContext, FunctionResource
from ixoncdkingress.types import FunctionArguments, FunctionLocation
from ixoncdkingress.webserver.config import Config
from ixoncdkingress.webserver.utils import (
    create_mongo_client,
    get_document_db_collection_name,
    parse_function_location,
)

try:
    from dominate.document import document
    from dominate.tags import (
        div,
        form,
        h1,
        h3,
        input_,
        label,
        table,
        tbody,
        td,
        textarea,
        th,
        thead,
        tr,
    )

    HAS_DOMINATE = True # pragma: no cover
except ImportError: # pragma: no cover
    HAS_DOMINATE = False

if TYPE_CHECKING:
    from dominate import tags

def generate_form(pre_fill: dict[str, str], form_output: str | None = None) -> str:
    """
    Generates and renders the CDK Ingress HTML form
    """
    return _generate_form_document(pre_fill, form_output).render()

def _generate_form_document(
        pre_fill: dict[str, str],
        form_output: str | None = None
    ) -> 'document':
    """
    Generates the CDK Ingress HTML form document
    """
    if not HAS_DOMINATE:
        raise Exception('Requires dominate package. pip install dominate')

    doc = document(title='IXON CDK Ingress')

    with doc:
        h1('IXON CDK Ingress')
        with form(method='post'):
            with div(class_name='function'):
                label('Function', _for='function')
                input_(type='text', id='function', name='function', size='32', required='true',
                       value=pre_fill.get('function'))
            with div(class_name='arguments'):
                h3('Function input')
                _custom_properties(pre_fill, prefix='function_args__')

            with div(class_name='resource'):
                h3('User')
                _resource(pre_fill, prefix='user__', show_permission=False)
            with div(class_name='resource'):
                h3('Company')
                _resource(pre_fill, prefix='company__', show_permission=True)
            with div(class_name='resource'):
                h3('Asset')
                _resource(pre_fill, prefix='asset__', show_permission=True)
            with div(class_name='resource'):
                h3('Agent')
                _resource(pre_fill, prefix='agent__', show_permission=True)

            input_(type='submit', value='Run function')
            input_(type='reset', value='Clear form')

        h3('Function output')
        textarea(form_output or 'Function output goes here', rows='10', cols='80')

    return doc

def _custom_properties(pre_fill: dict[str, str], prefix: str) -> 'tags.html_tag':
    """
    Generates a form fragment for custom properties.
    """
    table_tag = table()

    with table_tag:
        with thead():
            th('Key')
            th('Value')
        with tbody():
            for i in range(9):
                with tr():
                    td(input_(type='text', name=f'{prefix}key{i}', id=f'{prefix}key{i}',
                              size='16', value=pre_fill.get(f'{prefix}key{i}')))
                    td(input_(type='text', name=f'{prefix}value{i}', id=f'{prefix}value{i}',
                              size='32', value=pre_fill.get(f'{prefix}value{i}')))

    return table_tag

def _permissions(pre_fill: dict[str, str], prefix: str) -> 'tags.html_tag':
    """
    Generates a form fragment for permissions.
    """
    table_tag = table()

    with table_tag, tbody():
        for i in range(9):
            with tr():
                td(input_(type='text', name=f'{prefix}value{i}', id=f'{prefix}value{i}',
                          size='32', value=pre_fill.get(f'{prefix}value{i}')))

    return table_tag

def _resource(pre_fill: dict[str, str], prefix: str, show_permission: bool) -> 'tags.html_tag':
    """
    Generates a form fragment for a context resource.
    """
    table_tag = table()

    with table_tag, tbody():
        with tr():
            td(label('PublicID', _for=f'{prefix}public_id'))
            td(input_(type='text', id=f'{prefix}public_id', name=f'{prefix}public_id',
                      size='12', value=pre_fill.get(f'{prefix}public_id')))
        with tr():
            td(label('Name', _for=f'{prefix}name'))
            td(input_(type='text', id=f'{prefix}name', name=f'{prefix}name', size='32',
                      value=pre_fill.get(f'{prefix}name')))
        with tr():
            td('Custom properties')
            td(_custom_properties(pre_fill, f'{prefix}custom_properties__'))

        if show_permission:
            td('Permissions')
            td(_permissions(pre_fill, f'{prefix}permissions__'))

    return table_tag

def parse_form_input(
        config: Config,
        context_config: dict[str, str],
        body: str) -> tuple[FunctionContext, FunctionLocation, FunctionArguments, str]:
    """
    Parses an application/x-www-form-urlencoded request body string into a context, function
    location and function arguments.
    """

    in_put = parse_qs(body)

    context = FunctionContext(
        context_config,
        api_client=ApiClient(config.api_client_base_url),
        mongo_client=create_mongo_client(config, {}),
        user=_parse_resource('user__', in_put, False),
        company=_parse_resource('company__', in_put, True),
        asset=_parse_resource('asset__', in_put, True),
        agent=_parse_resource('agent__', in_put, True),
        document_db_collection_name=get_document_db_collection_name()
    )

    function_location = parse_function_location(in_put.get('function', [''])[0])
    function_arguments = _parse_function_args(in_put)

    return context, function_location, function_arguments, ''

def _parse_function_args(in_put: dict[str, list[str]]) -> FunctionArguments:
    function_arguments = {}
    for in_put_key, in_put_value in in_put.items():
        if not in_put_key.startswith('function_args__key'):
            continue

        key_argument_value = f'function_args__value{in_put_key[18:]}'
        if key_argument_value not in in_put:
            continue

        function_arguments[in_put_value[0]] = in_put[key_argument_value][0]

    return function_arguments

def _parse_resource(
        prefix: str,
        in_put: dict[str, list[str]],
        has_permissions: bool
    ) -> FunctionResource | None:
    """
    Reads a resource description from the in_put and returns it as a FunctionResource.
    """
    props = {}
    permissions: set[str] | None = set() if has_permissions else None
    if not in_put.get(f'{prefix}public_id') or not in_put.get(f'{prefix}name'):
        return None

    custom_property_prefix = f'{prefix}custom_properties__'
    permission_prefix = f'{prefix}permissions__value'
    for in_put_key, in_put_value in in_put.items():

        # Process custom property
        if in_put_key.startswith(f'{custom_property_prefix}key'):
            custom_property_index = in_put_key[len(f'{custom_property_prefix}key'):]
            key_custom_property_value = f'{custom_property_prefix}value{custom_property_index}'
            if key_custom_property_value not in in_put:
                continue

            props[in_put_value[0]] = in_put[key_custom_property_value][0]
            continue

        # Process permission
        if has_permissions and in_put_key.startswith(permission_prefix):
            assert isinstance(permissions, set) # type hint
            permissions.add(in_put_value[0])

    return FunctionResource(in_put[f'{prefix}public_id'][0],
                            in_put[f'{prefix}name'][0],
                            props,
                            permissions)
