import logging

from inflection import singularize

from .views import generate_rest_view
from .acl import generate_acl
from .utils import (
    is_dynamic_uri, resource_view_attrs, generate_model_name,
    dynamic_part_name, attr_subresource, singular_subresource,
    get_static_parent)


log = logging.getLogger(__name__)


def _get_nefertari_parent_resource(
        raml_resource, generated_resources, default):
    parent_raml_res = get_static_parent(raml_resource)
    if parent_raml_res is not None:
        parent_resource = generated_resources.get(parent_raml_res.path)
        return parent_resource or default
    return default


def generate_resource(raml_resource, parent_resource):
    """ Perform complete one resource configuration process

    This function generates: ACL, view, route, resource, database
    model for a given `raml_resource`. New nefertari resource is
    attached to `parent_resource` class which is an instance of
    `nefertari.resource.Resource`.

    Things to consider:
      * Top-level resources must be collection names.
      * No resources are explicitly created for dynamic (ending with '}')
        RAML resources as they are implicitly processed by parent collection
        resources.
      * Resource nesting must look like collection/id/collection/id/...
      * Only part of resource path after last '/' is taken into account,
        thus each level of resource nesting should add one more path
        element. E.g. /stories -> /stories/{id} and not
        /stories -> /stories/mystories/{id}. Latter route will be generated
        at /stories/{id}.

    :param raml_resource: Instance of Instance of
        ramlfications.raml.ResourceNode.
    :param parent_resource: Parent nefertari resource object.
    """
    from .models import get_existing_model

    # Don't generate resources for dynamic routes as they are already
    # generated by their parent
    resource_uri = raml_resource.path.split('/')[-1].strip()
    if is_dynamic_uri(resource_uri):
        if parent_resource.is_root:
            raise Exception("Top-level resources can't be dynamic and must "
                            "represent collections instead")
        return

    clean_uri = route_name = resource_uri.strip('/')
    log.info('Configuring resource: `{}`. Parent: `{}`'.format(
        route_name, parent_resource.uid or 'root'))

    # Get DB model. If this is an attribute or singular resource,
    # we don't need to get model
    is_singular = singular_subresource(raml_resource, route_name)
    is_attr_res = attr_subresource(raml_resource, route_name)
    if not parent_resource.is_root and (is_attr_res or is_singular):
        model_cls = parent_resource.view.Model
    else:
        model_name = generate_model_name(route_name)
        model_cls = get_existing_model(model_name)

    resource_kwargs = {}

    # Generate ACL
    log.info('Generating ACL for `{}`'.format(route_name))
    resource_kwargs['factory'] = generate_acl(
        model_cls=model_cls,
        raml_resource=raml_resource)

    # Generate dynamic part name
    if not is_singular:
        resource_kwargs['id_name'] = dynamic_part_name(
            raml_resource=raml_resource,
            clean_uri=clean_uri,
            pk_field=model_cls.pk_field())

    # Generate REST view
    log.info('Generating view for `{}`'.format(route_name))
    view_attrs = resource_view_attrs(raml_resource, is_singular)
    resource_kwargs['view'] = generate_rest_view(
        model_cls=model_cls,
        attrs=view_attrs,
        attr_view=is_attr_res,
        singular=is_singular,
    )

    # In case of singular resource, model still needs to be generated,
    # but we store it on a different view attribute
    if is_singular:
        model_name = generate_model_name(route_name)
        resource_kwargs['view']._singular_model = get_existing_model(
            model_name)

    # Create new nefertari resource
    log.info('Creating new resource for `{}`'.format(route_name))
    resource_args = (singularize(clean_uri),)

    if not is_singular:
        resource_args += (clean_uri,)

    return parent_resource.add(*resource_args, **resource_kwargs)


def generate_server(raml_root, config):
    """ Handle server generation process.

    :param raml_root: Instance of ramlfications.raml.RootNode.
    :param config: Pyramid Configurator instance.
    """
    log.info('Server generation started')

    if not raml_root.resources:
        return

    root_resource = config.get_root_resource()
    generated_resources = {}

    for raml_resource in raml_root.resources:
        if raml_resource.path in generated_resources:
            continue

        # Get Nefertari parent resource
        parent_resource = _get_nefertari_parent_resource(
            raml_resource, generated_resources, root_resource)

        # Get generated resource and store it
        new_resource = generate_resource(raml_resource, parent_resource)
        if new_resource is not None:
            generated_resources[raml_resource.path] = new_resource


def generate_models(config, raml_resources):
    """ Generate model for each resource in :raml_resources:

    The DB model name is generated using singular titled version of current
    resource's url. E.g. for resource under url '/stories', model with
    name 'Story' will be generated.

    :param config: Pyramid Configurator instance.
    :param raml_resources: List of ramlfications.raml.ResourceNode.
    """
    from .models import handle_model_generation
    if not raml_resources:
        return

    for raml_resource in raml_resources:
        # No need to generate models for dynamic resource
        if is_dynamic_uri(raml_resource.path):
            continue

        # Since POST resource must define schema use only POST
        # resources to generate models
        if raml_resource.method.upper() != 'POST':
            continue

        # Generate DB model
        # If this is an attribute resource we don't need to generate model
        route_name = raml_resource.path.split('/')[-1]
        route_name = route_name.strip('/')
        if not attr_subresource(raml_resource, route_name):
            log.info('Configuring model for route `{}`'.format(route_name))
            model_cls, is_auth_model = handle_model_generation(
                raml_resource, route_name)
            if is_auth_model:
                config.registry.auth_model = model_cls
