# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import flask
import flask_restful
from flask_restful import fields

from tuning_box import db
from tuning_box import errors
from tuning_box import library

environment_fields = {
    'id': fields.Integer,
    'components': fields.List(fields.Integer(attribute='id')),
    'hierarchy_levels': fields.List(fields.String(attribute='name')),
}


def prepare_env_for_output(env):
    # Proper order of levels can't be provided by ORM backref
    hierarchy_levels = db.EnvironmentHierarchyLevel.get_for_environment(env)
    return {'id': env.id, 'components': env.components,
            'hierarchy_levels': hierarchy_levels}


class EnvironmentsCollection(flask_restful.Resource):
    method_decorators = [flask_restful.marshal_with(environment_fields)]

    def get(self):
        envs = db.Environment.query.order_by(db.Environment.id).all()
        result = []
        for env in envs:
            result.append(prepare_env_for_output(env))
        return result, 200

    def _check_components(self, components):
        identities = set()
        duplicates = set()
        id_names = ('id', 'name')
        for component in components:
            for id_name in id_names:
                value = getattr(component, id_name)

                if value not in identities:
                    identities.add(value)
                else:
                    duplicates.add(value)
        if duplicates:
            raise errors.TuningboxIntegrityError(
                "Components duplicates: {0}".format(duplicates))

    @db.with_transaction
    def post(self):
        component_ids = flask.request.json['components']
        components = [db.Component.query.get_by_id_or_name(i)
                      for i in component_ids]
        self._check_components(components)

        hierarchy_levels = []
        level = None
        for name in flask.request.json['hierarchy_levels']:
            level = db.EnvironmentHierarchyLevel(name=name, parent=level)
            hierarchy_levels.append(level)

        environment = db.Environment(components=components,
                                     hierarchy_levels=hierarchy_levels)
        if 'id' in flask.request.json:
            environment.id = flask.request.json['id']
        db.db.session.add(environment)
        return prepare_env_for_output(environment), 201


class Environment(flask_restful.Resource):
    method_decorators = [flask_restful.marshal_with(environment_fields)]

    def get(self, environment_id):
        env = db.get_or_404(db.Environment, environment_id)
        return prepare_env_for_output(env), 200

    def _update_components(self, environment, components):
        if components is not None:
            new_components = library.load_objects_by_id_or_name(
                db.Component, components)
            environment.components = new_components

    def _update_hierarchy_levels(self, environment, hierarchy_levels_names):
        if hierarchy_levels_names is not None:
            old_hierarchy_levels = db.EnvironmentHierarchyLevel.query.filter(
                db.EnvironmentHierarchyLevel.environment_id == environment.id
            ).all()

            new_hierarchy_levels = []

            for level_name in hierarchy_levels_names:
                level = db.get_or_create(
                    db.EnvironmentHierarchyLevel,
                    name=level_name,
                    environment=environment
                )
                new_hierarchy_levels.append(level)

            parent_id = None
            for level in new_hierarchy_levels:
                level.parent_id = parent_id
                parent_id = level.id
            for old_level in old_hierarchy_levels:
                if old_level not in new_hierarchy_levels:
                    db.db.session.delete(old_level)
            environment.hierarchy_levels = new_hierarchy_levels

    @db.with_transaction
    def _perform_update(self, environment_id):
        environment = db.get_or_404(db.Environment, environment_id)
        update_by = flask.request.json

        components = update_by.get('components')
        self._update_components(environment, components)

        hierarchy_levels = update_by.get('hierarchy_levels')
        self._update_hierarchy_levels(environment, hierarchy_levels)

    def put(self, environment_id):
        return self.patch(environment_id)

    def patch(self, environment_id):
        self._perform_update(environment_id)
        return None, 204

    @db.with_transaction
    def delete(self, environment_id):
        environment = db.get_or_404(db.Environment, environment_id)
        db.db.session.delete(environment)
        return None, 204
