"""
The HEA Settings Service manages user and system settings.
"""
import logging

from heaserver.service import response, appproperty, client
from heaserver.service.runner import init_cmd_line, routes, start, web
from heaserver.service.db import mongo, mongoservicelib
from heaserver.service.wstl import builder_factory, action
from heaserver.service.oidcclaimhdrs import SUB
from heaobject.settings import SettingsObject
from heaobject.root import DesktopObjectDict, Permission
from heaobject.user import NONE_USER
from heaobject.person import Person
from heaobject.registry import Collection
from heaobject.keychain import Credentials
from typing import AsyncIterator
from copy import deepcopy
from aiohttp import hdrs

MONGODB_SETTINGS_COLLECTION = 'settings'

DEFAULT_SETTINGS_OBJECT_TEMPLATES: dict[str, DesktopObjectDict] = {
    'heasettings|profile': {
        'name': 'heasettings|profile',
        'display_name': 'Profile',
        'type': SettingsObject.get_type_name(),
        'owner': NONE_USER,
        'shares': [{
            'type': 'heaobject.root.ShareImpl',
            'permissions': [Permission.VIEWER.name, Permission.EDITOR.name, Permission.CHECK_DYNAMIC.name]
        }],
        'actual_object_type_name': Person.get_type_name(),
        'actual_object_uri': 'people/me',
        'actual_object_id': 'me'
    },
    'heasettings|credentials': {
        'name': 'heasettings|credentials',
        'display_name': 'Credentials',
        'type': SettingsObject.get_type_name(),
        'owner': NONE_USER,
        'shares': [{
            'type': 'heaobject.root.ShareImpl',
            'permissions': [Permission.VIEWER.name, Permission.EDITOR.name, Permission.CHECK_DYNAMIC.name]
        }],
        'actual_object_type_name': Collection.get_type_name(),
        'actual_object_uri': f'collections/{Credentials.get_type_name()}',
        'actual_object_id': Credentials.get_type_name()
    }
}


@routes.get('/settingsping')
async def ping(request: web.Request) -> web.Response:
    """
    Checks if this service is running.

    :param request: the HTTP request.
    :return: the HTTP response.
    """
    return await mongoservicelib.ping(request)


@routes.get('/settings/{id}')
@action('heaserver-settings-settings-object-get-properties', rel='hea-properties')
@action('heaserver-settings-settings-object-get-open-choices', rel='hea-opener-choices', path='settings/{id}/opener')
@action('heaserver-settings-settings-object-get-self', rel='self', path='settings/{id}')
@action('heaserver-settings-settings-object-get-actual', rel='hea-actual', path='{+actual_object_uri}')
async def get_settings_object(request: web.Request) -> web.Response:
    """
    Gets the settings object with the specified id.
    :param request: the HTTP request.
    :return: the requested settings object or Not Found.
    ---
    summary: A specific settings object, by id.
    tags:
        - heaserver-settings
    parameters:
        - $ref: '#/components/parameters/id'
    responses:
      '200':
        $ref: '#/components/responses/200'
      '404':
        $ref: '#/components/responses/404'
    """
    return await _get_settings_object(request)


@routes.get('/settings/byname/{name}')
@action('heaserver-settings-settings-object-get-self', rel='self', path='settings/{id}')
async def get_settings_object_by_name(request: web.Request) -> web.Response:
    """
    Gets the settings object with the specified id.
    :param request: the HTTP request.
    :return: the requested settings object or Not Found.
    ---
    summary: A specific settings object, by name.
    tags:
        - heaserver-settings
    parameters:
        - name: name
          in: path
          required: true
          description: The name of the settings object.
          schema:
            type: string
          examples:
            example:
              summary: A settings object name
              value: heasettings|credentials
    responses:
      '200':
        $ref: '#/components/responses/200'
      '404':
        $ref: '#/components/responses/404'
    """
    obj = await mongoservicelib.get_by_name_dict(request, MONGODB_SETTINGS_COLLECTION)
    if obj is None:
        obj = await _default_settings_object_by_name(request, request.match_info['name'])

    return await response.get(request, obj)


@routes.get('/settings')
@routes.get('/settings/')
@action('heaserver-settings-settings-object-get-properties', rel='hea-properties')
@action('heaserver-settings-settings-object-get-open-choices', rel='hea-opener-choices', path='settings/{id}/opener')
@action('heaserver-settings-settings-object-get-self', rel='self', path='settings/{id}')
@action('heaserver-settings-settings-object-get-actual', rel='hea-actual', path='{+actual_object_uri}')
async def get_all_settings_objects(request: web.Request) -> web.Response:
    """
    Gets all settings objects.
    :param request: the HTTP request.
    :return: all settings objects.
    ---
    summary: All settings objects.
    tags:
        - heaserver-settings
    responses:
      '200':
        $ref: '#/components/responses/200'
    """
    objs = await mongoservicelib.get_all_dict(request, MONGODB_SETTINGS_COLLECTION)
    missing = set(DEFAULT_SETTINGS_OBJECT_TEMPLATES.keys()).difference(obj.get('name') for obj in objs)
    if missing:
        objs_ = list(objs)
        for m in missing:
            if m in DEFAULT_SETTINGS_OBJECT_TEMPLATES:
                objs_.append(await _default_settings_object_by_name(request, m))
    else:
        objs_ = objs
    return await response.get_all(request, objs_)


@routes.post('/settings')
@routes.post('/settings/')
async def post_settings_object(request: web.Request) -> web.Response:
    """
    Posts the provided settings object.
    :param request: the HTTP request.
    :return: a Response object with a status of Created and the object's URI in the Location header.
    ---
    summary: Settings object creation
    tags:
        - heaserver-settings
    requestBody:
      description: A new settings object.
      required: true
      content:
        application/vnd.collection+json:
          schema:
            type: object
          examples:
            example:
              summary: Settings object example
              value: {
                "template": {
                  "data": [{
                    "name": "created",
                    "value": null
                  },
                  {
                    "name": "derived_by",
                    "value": null
                  },
                  {
                    "name": "derived_from",
                    "value": []
                  },
                  {
                    "name": "description",
                    "value": null
                  },
                  {
                    "name": "display_name",
                    "value": "Joe"
                  },
                  {
                    "name": "modified",
                    "value": null
                  },
                  {
                    "name": "name",
                    "value": "joe"
                  },
                  {
                    "name": "owner",
                    "value": "system|none"
                  },
                  {
                    "name": "shares",
                    "value": []
                  },
                  {
                    "name": "source",
                    "value": null
                  },
                  {
                    "name": "type",
                    "value": "heaobject.settings.SettingsObject"
                  }]
                }
              }
        application/json:
          schema:
            type: object
          examples:
            example:
              summary: Settings object example
              value: {
                "created": null,
                "derived_by": null,
                "derived_from": [],
                "description": null,
                "display_name": "Joe",
                "modified": null,
                "name": "joe",
                "owner": "system|none",
                "shares": [],
                "source": null,
                "type": "heaobject.settings.SettingsObject"
              }
    responses:
      '201':
        $ref: '#/components/responses/201'
      '400':
        $ref: '#/components/responses/400'
      '404':
        $ref: '#/components/responses/404'
    """
    return await mongoservicelib.post(request, MONGODB_SETTINGS_COLLECTION, SettingsObject)


@routes.put('/settings/{id}')
async def put_settings_object(request: web.Request) -> web.Response:
    """
    Updates the settings object with the specified id.
    :param request: the HTTP request.
    :return: a Response object with a status of No Content or Not Found.
    ---
    summary: Settings object updates
    tags:
        - heaserver-settings
    parameters:
        - $ref: '#/components/parameters/id'
    requestBody:
      description: An updated settings object.
      required: true
      content:
        application/vnd.collection+json:
          schema:
            type: object
          examples:
            example:
              summary: Settings object example
              value: {
                "template": {
                  "data": [{
                    "name": "created",
                    "value": null
                  },
                  {
                    "name": "derived_by",
                    "value": null
                  },
                  {
                    "name": "derived_from",
                    "value": []
                  },
                  {
                    "name": "description",
                    "value": null
                  },
                  {
                    "name": "display_name",
                    "value": "Reximus Max"
                  },
                  {
                    "name": "modified",
                    "value": null
                  },
                  {
                    "name": "name",
                    "value": "reximus"
                  },
                  {
                    "name": "owner",
                    "value": "system|none"
                  },
                  {
                    "name": "shares",
                    "value": []
                  },
                  {
                    "name": "source",
                    "value": null
                  },
                  {
                    "name": "version",
                    "value": null
                  },
                  {
                    "name": "base_url",
                    "value": "http://localhost/foo"
                  },
                  {
                    "section": "resources",
                    "index": 0,
                    "name": "resource_type_name",
                    "value": "heaobject.folder.Folder",
                  },
                  {
                    "section": "resources",
                    "index": 0,
                    "name": "type",
                    "value": "heaobject.registry.Resource",
                  },
                  {
                    "section": "resources",
                    "index": 0,
                    "name": "base_path",
                    "value": "/folders"
                  },
                  {
                   "section": "resources",
                    "index": 0,
                    "name": "file_system_name",
                    "value": "DEFAULT_MONGODB"
                  },
                  {
                  "name": "id",
                  "value": "666f6f2d6261722d71757578"
                  },
                  {
                  "name": "type",
                  "value": "heaobject.settings.SettingsObject"
                  }]
                }
              }
        application/json:
          schema:
            type: object
          examples:
            example:
              summary: Settings object example
              value: {
                "id": "666f6f2d6261722d71757578",
                "created": null,
                "derived_by": null,
                "derived_from": [],
                "description": null,
                "display_name": "Reximus Max",
                "modified": null,
                "name": "reximus",
                "owner": "system|none",
                "shares": [],
                "source": null,
                "type": "heaobject.settings.SettingsObject",
                "version": null,
                "base_url": "http://localhost/foo",
                "resources": [{
                    "type": "heaobject.registry.Resource",
                    "resource_type_name": "heaobject.folder.Folder",
                    "base_path": "/folders",
                    "file_system_name": "DEFAULT_MONGODB"
                }]
              }
    responses:
      '204':
        $ref: '#/components/responses/204'
      '400':
        $ref: '#/components/responses/400'
      '404':
        $ref: '#/components/responses/404'
    """
    return await mongoservicelib.put(request, MONGODB_SETTINGS_COLLECTION, SettingsObject)


@routes.delete('/settings/{id}')
async def delete_settings_object(request: web.Request) -> web.Response:
    """
    Deletes the settings object with the specified id.
    :param request: the HTTP request.
    :return: No Content or Not Found.
    ---
    summary: Settings object deletion
    tags:
        - heaserver-settings
    parameters:
        - $ref: '#/components/parameters/id'
    responses:
      '204':
        $ref: '#/components/responses/204'
      '404':
        $ref: '#/components/responses/404'
    """
    return await mongoservicelib.delete(request, MONGODB_SETTINGS_COLLECTION)


@routes.get('/settings/{id}/opener')
@action('heaserver-settings-settings-object-open', rel=f'hea-opener hea-default', path='{+actual_object_uri}')
async def get_settings_object_opener(request: web.Request) -> web.Response:
    """
    Gets a settings object with a default link to open it, if the format in the Accept header supports links.

    :param request: the HTTP Request.
    :return: A Response object with a status of Multiple Choices or Not Found.
    ---
    summary: Settings object opener choices
    tags:
        - heaserver-settings
    parameters:
        - $ref: '#/components/parameters/id'
    responses:
      '300':
        $ref: '#/components/responses/300'
      '404':
        $ref: '#/components/responses/404'
    """
    return await _get_settings_object(request)



def main() -> None:
    config = init_cmd_line(description='Management of user and system settings',
                           default_port=8080)
    start(package_name='heaserver-settings', db=mongo.MongoManager,
          wstl_builder_factory=builder_factory(__package__), config=config)

async def _default_settings_object_by_name(request: web.Request, name: str) -> DesktopObjectDict | None:
    obj = deepcopy(DEFAULT_SETTINGS_OBJECT_TEMPLATES.get(name))
    if obj is not None:
        obj['user'] = request.headers.get(SUB, NONE_USER)
        obj['shares'][0]['user'] = request.headers.get(SUB, NONE_USER)
        id_ = await mongoservicelib.post_dict_return_id(request, obj, MONGODB_SETTINGS_COLLECTION, SettingsObject)
        if id_ is None:
            raise IOError('Failed to insert default settings object into database')
        else:
            obj['id'] = id_
            return obj
    else:
        return None


async def _get_settings_object(request: web.Request) -> web.Response:
    obj = await mongoservicelib.get_dict(request, MONGODB_SETTINGS_COLLECTION)
    if obj is None:
        obj = await _default_settings_object_by_name(request, request.match_info['id'])

    return await response.get(request, obj)
