"""
Test HEA service.
"""
from heaobject.organization import Organization

from heaserver.service import response
from heaserver.service.heaobjectsupport import new_heaobject_from_type
from heaserver.service.runner import routes, web
from heaserver.service.db import mongoservicelib
from heaserver.service.wstl import action
from heaserver.service import appproperty
from heaobject.registry import Component
from heaobject.volume import MongoDBFileSystem, DEFAULT_FILE_SYSTEM
from heaobject.error import DeserializeException
from io import StringIO


MONGODB_COMPONENT_COLLECTION = 'components'
MONGODB_ORGANIZATION_COLLECTION = 'organizations'


@routes.get('/components/{id}')
@action('component-get-properties', rel='hea-properties')
@action('component-get-open-choices', rel='hea-opener-choices', path='components/{id}/opener')
@action('component-duplicate', rel='hea-duplicator', path='components/{id}/duplicator')
async def get_component(request: web.Request) -> web.Response:
    """
    Gets the component with the specified id.
    :param request: the HTTP request.
    :return: the requested component or Not Found.
    """
    return await mongoservicelib.get(request, MONGODB_COMPONENT_COLLECTION)


@routes.get('/components/byname/{name}')
async def get_component_by_name(request: web.Request) -> web.Response:
    """
    Gets the component with the specified name.
    :param request: the HTTP request.
    :return: the requested component or Not Found.
    """
    return await mongoservicelib.get_by_name(request, MONGODB_COMPONENT_COLLECTION)


@routes.get('/components/bytype/{type}')
@routes.get('/components/bytype/{type}/byfilesystemtype/{filesystemtype}')
@routes.get('/components/bytype/{type}/byfilesystemtype/{filesystemtype}/byfilesystemname/{filesystemname}')
async def get_components_by_type(request: web.Request) -> web.Response:
    """
    Gets the component that serves resources of the specified HEA object type.
    :param request: the HTTP request.
    :return: the requested component or Not Found.
    """
    if 'filesystemname' in request.match_info:
        file_system_name = request.match_info['filesystemname']
    else:
        file_system_name = DEFAULT_FILE_SYSTEM
    if file_system_name == DEFAULT_FILE_SYSTEM:
        query_clause = {
            '$or': [{'file_system_name': {'$exists': False}}, {'file_system_name': {'$in': [file_system_name, None]}}]}
    else:
        query_clause = {'file_system_name': {'$eq': file_system_name}}
    if 'filesystemtype' in request.match_info:
        file_system_type = request.match_info['filesystemtype']
    else:
        file_system_type = MongoDBFileSystem.get_type_name()
    if file_system_type == MongoDBFileSystem.get_type_name():
        query_clause.update({'$or': [{'file_system_type': {'$exists': False}},
                                     {'file_system_type': {'$in': [file_system_type, None]}}]})
    else:
        query_clause.update({'file_system_type': {'$eq': file_system_type}})
    mongo_attributes = {'resources': {
        '$elemMatch': {
            'resource_type_name': {'$eq': request.match_info['type']},
            **query_clause
        }}}
    result = await request.app[appproperty.HEA_DB].get(request,
                                                       MONGODB_COMPONENT_COLLECTION,
                                                       mongoattributes=mongo_attributes)
    return await response.get(request, result)


@routes.get('/components')
@routes.get('/components/')
@action('component-get-properties', rel='hea-properties')
@action('component-get-open-choices', rel='hea-opener-choices', path='components/{id}/opener')  # only add if the object is openable
@action('component-duplicate', rel='hea-duplicator', path='components/{id}/duplicator')
async def get_all_components(request: web.Request) -> web.Response:
    """
    Gets all components.

    :param request: the HTTP request.
    :return: all components.
    """
    return await mongoservicelib.get_all(request, MONGODB_COMPONENT_COLLECTION)


@routes.get('/components/{id}/duplicator')
@action(name='component-duplicate-form')
async def get_component_duplicator(request: web.Request) -> web.Response:
    """
    Gets a form template for duplicating the requested component.

    :param request: the HTTP request. Required.
    :return: the requested form, or Not Found if the requested component was not found.
    """
    return await mongoservicelib.get(request, MONGODB_COMPONENT_COLLECTION)


@routes.post('/components/duplicator')
async def post_component_duplicator(request: web.Request) -> web.Response:
    """
    Posts the provided component for duplication.

    :param request: the HTTP request.
    :return: a Response object with a status of Created and the object's URI in the
    """
    return await mongoservicelib.post(request, MONGODB_COMPONENT_COLLECTION, Component)


@routes.get('/components/{id}/opener')
@action('component-open-default', rel='hea-default hea-opener text/plain', path='components/{id}/content')
async def get_component_opener(request: web.Request) -> web.Response:
    """
    Gets choices for opening the component's content. We wouldn't ordinarily open a component's content because
    components do not have any content.

    :param request: the HTTP request. Required.
    :return: the requested form, or Not Found if the requested component was not found.
    """
    return await mongoservicelib.opener(request, MONGODB_COMPONENT_COLLECTION)


@routes.get('/components/{id}/content')
async def get_component_content(request: web.Request) -> web.Response:
    """
    Gets the component's content. Registry components have no content; this is just for testing purposes.

    :param request: the HTTP request. Required.
    :return: the requested content.
    """
    return await mongoservicelib.get_content(request, MONGODB_COMPONENT_COLLECTION)


@routes.post('/components')
@routes.post('/components/')
async def post_component(request: web.Request) -> web.Response:
    """
    Creates a new component, provided in the body.

    :param request: the HTTP request.
    :return: a Response object with a status of Created and the object's URI in the
    """
    with StringIO('The quick brown fox jumps over the lazy dog') as f:
        return await mongoservicelib.post(request, MONGODB_COMPONENT_COLLECTION, Component, default_content=f)


@routes.put('/components/{id}')
async def put_component(request: web.Request) -> web.Response:
    """
    Updates the component with the specified id.

    :param request: the HTTP request.
    :return: a Response object with a status of No Content or Not Found.
    """
    return await mongoservicelib.put(request, MONGODB_COMPONENT_COLLECTION, Component)


@routes.put('/components/{id}/content')
async def put_component_content(request: web.Request) -> web.Response:
    """
    Updates the component's content. Registry components have no content; this is just for testing purposes. There is
    no corresponding POST call because the content's initial creation happens automatically when a POST call to
    /components/ is sent.

    :param request: the HTTP request. Required.
    :return: the requested content.
    """
    return await mongoservicelib.put_content(request, MONGODB_COMPONENT_COLLECTION, Component)


@routes.delete('/components/{id}')
async def delete_component(request: web.Request) -> web.Response:
    """
    Deletes the component with the specified id and its associated content.

    :param request: the HTTP request.
    :return: No Content or Not Found.
    """
    return await mongoservicelib.delete(request, MONGODB_COMPONENT_COLLECTION)


@routes.get('/organizations/{id}')
@action('heaserver-organizations-organization-get-properties', rel='properties')
@action('heaserver-organizations-organization-get-open-choices', rel='hea-opener-choices', path='organizations/{id}/opener')
@action('heaserver-organizations-organization-duplicate', rel='duplicator', path='organizations/{id}/duplicator')
async def get_organization(request: web.Request) -> web.Response:
    """
    Gets the organization with the specified id.
    :param request: the HTTP request.
    :return: the requested organization or Not Found.
    ---
    summary: A specific organization.
    tags:
        - heaserver-organizations-get-organization
    parameters:
        - $ref: '#/components/parameters/id'
    responses:
      '200':
        $ref: '#/components/responses/200'
      '404':
        $ref: '#/components/responses/404'
    """
    return await mongoservicelib.get(request, MONGODB_ORGANIZATION_COLLECTION)


@routes.get('/organizations/byname/{name}')
async def get_organization_by_name(request: web.Request) -> web.Response:
    """
    Gets the organization with the specified id.
    :param request: the HTTP request.
    :return: the requested organization or Not Found.
    ---
    summary: A specific organization, by name.
    tags:
        - heaserver-organizations-get-organization-by-name
    parameters:
        - $ref: '#/components/parameters/name'
    responses:
      '200':
        $ref: '#/components/responses/200'
      '404':
        $ref: '#/components/responses/404'
    """
    return await mongoservicelib.get_by_name(request, MONGODB_ORGANIZATION_COLLECTION)


@routes.get('/organizations/{id}/duplicator')
@action(name='heaserver-organizations-organization-duplicate-form', path='organizations/{id}')
async def get_organization_duplicate_form(request: web.Request) -> web.Response:
    """
    Gets a form template for duplicating the requested organization.

    :param request: the HTTP request. Required.
    :return: the requested form, or Not Found if the requested organization was not found.
    """
    return await mongoservicelib.get(request, MONGODB_ORGANIZATION_COLLECTION)


@routes.get('/organizations/{id}/opener')
@action('heaserver-organizations-organization-open-aws-accounts', rel='hea-default hea-opener text/plain',
        path='organizations/{id}/content')
async def get_organization_opener(request: web.Request) -> web.Response:
    """

    :param request: the HTTP Request.
    :return: A Response object with a status of Multiple Choices or Not Found.
    ---
    summary: Organization opener choices
    tags:
        - heaserver-organizations-organization-get-open-choices
    parameters:
        - $ref: '#/components/parameters/id'
    responses:
      '300':
        $ref: '#/components/responses/300'
      '404':
        $ref: '#/components/responses/404'
    """
    return await mongoservicelib.opener(request, MONGODB_ORGANIZATION_COLLECTION)


@routes.get('/organizations')
@routes.get('/organizations/')
@action('heaserver-organizations-organization-get-properties', rel='properties')
@action('heaserver-organizations-organization-get-open-choices', rel='hea-opener-choices', path='organizations/{id}/opener')
@action('heaserver-organizations-organization-duplicate', rel='duplicator', path='organizations/{id}/duplicator')
async def get_all_organizations(request: web.Request) -> web.Response:
    """
    Gets all organizations.
    :param request: the HTTP request.
    :return: all organizations.
    ---
    summary: All organizations.
    tags:
        - heaserver-organizations-get-all-organizations
    responses:
      '200':
        $ref: '#/components/responses/200'
    """
    return await mongoservicelib.get_all(request, MONGODB_ORGANIZATION_COLLECTION)


@routes.get('/organizations/{id}/content')
async def get_organization_content(request: web.Request) -> web.Response:
    """
    Gets the component's content. Registry components have no content; this is just for testing purposes.

    :param request: the HTTP request. Required.
    :return: the requested content.
    """
    return await mongoservicelib.get_content(request, MONGODB_ORGANIZATION_COLLECTION)


@routes.put('/organizations/{id}')
async def put_organization(request: web.Request) -> web.Response:
    """
    Updates the organization with the specified id.
    :param request: the HTTP request.
    :return: a Response object with a status of No Content or Not Found.
    """
    try:
        obj = await new_heaobject_from_type(request, Organization)
    except (ValueError, DeserializeException) as e:
        raise response.status_bad_request(str(e))
    return await mongoservicelib.put(request, MONGODB_ORGANIZATION_COLLECTION, Organization, None, obj)


@routes.put('/organizations/{id}/content')
async def put_organization_content(request: web.Request) -> web.Response:
    """
    Updates the component's content. Registry components have no content; this is just for testing purposes. There is
    no corresponding POST call because the content's initial creation happens automatically when a POST call to
    /components/ is sent.

    :param request: the HTTP request. Required.
    :return: the requested content.
    """
    return await mongoservicelib.put_content(request, MONGODB_ORGANIZATION_COLLECTION, Organization)


@routes.post('/organizations')
@routes.post('/organizations/')
async def post_organization(request: web.Request) -> web.Response:
    """
    Creates a new organization, provided in the body.

    :param request: the HTTP request.
    :return: a Response object with a status of Created and the object's URI in the
    """
    try:
        obj = await new_heaobject_from_type(request, Organization)
    except (ValueError, DeserializeException) as e:
        raise response.status_bad_request(str(e))
    return await mongoservicelib.post_desktop_object(request, obj, MONGODB_ORGANIZATION_COLLECTION)



