# -*- coding: utf-8 -*-
# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
#
# 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.

"""SQLAlchemy storage backend."""

from oslo.config import cfg
from oslo.db import exception as db_exc
from oslo.db.sqlalchemy import session as db_session
from oslo.db.sqlalchemy import utils as db_utils

from saladier.common import exception
import saladier.db.sqlalchemy
from saladier.db.sqlalchemy import models
from saladier.openstack.common import log

CONF = cfg.CONF
LOG = log.getLogger(__name__)


_FACADE = None
_SESSION = None


def _create_facade_lazily():
    global _FACADE
    if _FACADE is None:
        _FACADE = db_session.EngineFacade.from_config(CONF)
    return _FACADE


def get_engine():
    facade = _create_facade_lazily()
    return facade.get_engine()


def get_session(**kwargs):
    """Reuse the same session so we can rollback for tests or get a new one

    This probably can get optimised.
    """
    recycle = False
    global _SESSION
    facade = _create_facade_lazily()

    if 'recycle' in kwargs:
        kwargs.pop('recycle')
        recycle = True

    session = facade.get_session(**kwargs)
    if recycle:
        _SESSION = session

    if _SESSION is not None:
        return _SESSION

    return session


def get_backend():
    """The backend is this module itself."""
    return Connection()


def model_query(model, *args, **kwargs):
    """Query helper for simpler session usage.

    :param session: if present, the session to use
    """

    session = kwargs.get('session') or get_session()
    query = session.query(model, *args)
    return query


def _paginate_query(model, limit=None, marker=None, sort_keys=None,
                    sort_dir=None, query=None):
    if type(sort_keys) is str:
        sort_keys = [sort_keys]

    if not query:
        query = model_query(model)
    query = db_utils.paginate_query(query, model, limit, sort_keys,
                                    marker=marker, sort_dir=sort_dir)
    return query.all()


def rollback():
    session = get_session()
    session.rollback()


class Connection(object):  # TODO(chmouel): base class
    """SqlAlchemy connection."""

    def create_product(self, name, team, contact):
        product = models.Product(name=name, team=team, contact=contact)

        try:
            product.save()
            return product
        except db_exc.DBDuplicateEntry:
            raise exception.ProductAlreadyExists(name)

    def delete_product(self, id):
        query = model_query(models.Product).filter(
            (models.Product.id == id) | (models.Product.name == id))
        query.delete()

    # TODO(chmou): Make those filters working (they don't do anything yet)
    def get_all_products(self, tenant_id, admin=False,
                         filters=None, limit=None,
                         marker=None, sort_key=None,
                         sort_dir=None):
        sort_key = sort_key or 'name'
        query = model_query(models.Product)
        # NOTE(chmou): This would list everthing when in admins
        if not admin:
            query = query.join(models.Subscriptions).filter(
                tenant_id == models.Subscriptions.tenant_id)
        return _paginate_query(models.Product, limit, marker,
                               sort_key, sort_dir, query)

    def get_product(self, id, tenant_id, admin=False):
        query = model_query(models.Product).filter(
            (models.Product.id == id) | (models.Product.name == id))
        if not admin:
            query = query.join(models.Subscriptions).filter(
                tenant_id == models.Subscriptions.tenant_id)
        try:
            return query.one()
        except saladier.db.sqlalchemy.exc.NoResultFound:
            raise exception.ProductNotFound(name=id)

    def create_product_version(self, product_id, version, url):
        query = model_query(models.ProductVersion)
        if query.filter(models.Product.id == product_id).filter_by(
                version=version).all():
            raise exception.ProductVersionAlreadyExists(product_id)
        new = models.ProductVersion(version=version,
                                    product_id=product_id,
                                    url=url)
        new.save()
        return new

    def get_product_version_by_id(self, id, session=None):
        query = model_query(models.ProductVersion,
                            session=session).filter_by(id=id)
        try:
            return query.one()
        except saladier.db.sqlalchemy.exc.NoResultFound:
            raise exception.ProductVersionNotFound(name=id)

    def get_all_product_versions(self, product_id):
        query = model_query(models.ProductVersion)
        res = query.filter(models.Product.id == product_id).all()
        # TODO(chmou): filtering/paging and such
        return res

    def delete_product_versions(self, product_id, version):
        query = model_query(models.ProductVersion).filter_by(
            product_id=product_id, version=version)
        query.delete()

    def create_platform(self, name, location, contact, tenant_id):
        platform = models.Platform(name=name, location=location,
                                   contact=contact, tenant_id=tenant_id)

        try:
            platform.save()
            return platform
        except db_exc.DBDuplicateEntry:
            raise exception.PlatformAlreadyExists(name)

    def delete_platform(self, id):
        query = model_query(models.Platform).filter(
            (models.Platform.id == id) | (models.Platform.name == id))
        query.delete()

    def get_all_platforms(self, filters=None, limit=None, marker=None,
                          sort_key=None, sort_dir=None):
        sort_key = sort_key or 'name'
        query = model_query(models.Platform)
        return _paginate_query(models.Platform, limit, marker,
                               sort_key, sort_dir, query)

    def get_platform(self, id):
        query = model_query(models.Platform).filter(
            (models.Platform.id == id) | (models.Platform.name == id))
        try:
            return query.one()
        except saladier.db.sqlalchemy.exc.NoResultFound:
            raise exception.PlatformNotFound(name=id)

    # -*- Subscriptions -*-
    def create_subscription(self, tenant_id, product_id):
        query = models.Subscriptions(tenant_id=tenant_id,
                                     product_id=product_id)
        try:
            query.save()
        except db_exc.DBDuplicateEntry:
            raise exception.SubscriptionAlreadyExists(product_id)

    def delete_subscription(self, product_id, tenant_id):
        model = (model_query(models.Subscriptions).
                 filter_by(product_id=product_id).
                 filter_by(tenant_id=tenant_id))
        model.delete()

    def get_subscriptions_for_tenant_id(self, tenant_id):
        query = model_query(models.Subscriptions).filter_by(
            tenant_id=tenant_id)
        return query.all()

    def add_version_status(self, platform_id, product_version_id, status,
                           logs_location):
        versions_status = (model_query(models.ProductVersionStatus).
                           filter_by(platform_id=platform_id).
                           filter_by(product_version_id=product_version_id).
                           first())

        if versions_status:
            raise exception.ProductVersionStatusAlreadyExists(
                "%s,%s" % (platform_id, product_version_id))

        session = get_session()
        with session.begin(subtransactions=True):
            platform = self.get_platform(platform_id)
            product_version = self.get_product_version_by_id(
                product_version_id, session=session)
            product_version_status = models.ProductVersionStatus(
                status=status, logs_location=logs_location)
            product_version_status.platform = platform
            product_version.platforms.append(product_version_status)
            try:
                product_version.save(session)
            except exception.SaladierFlushError:
                # NOTE(Gonéri): I think this should never happend
                raise exception.ProductVersionStatusAlreadyExists(
                    name="%s, %s" % (platform_id, product_version_id))

    def get_version_status(self, platform_id, product_version_id):
        query = model_query(models.ProductVersionStatus)
        try:
            return query.filter_by(platform_id=platform_id,
                                   product_version_id=product_version_id).one()
        except saladier.db.sqlalchemy.exc.NoResultFound:
            raise exception.ProductVersionStatusNotFound(
                "name=%s,%s" % (platform_id, product_version_id))

    def get_all_status_by_version_id(self, product_version_id):
        query = model_query(models.ProductVersionStatus)
        try:
            return query.filter_by(product_version_id=product_version_id).all()
        except saladier.db.sqlalchemy.exc.NoResultFound:
            raise exception.ProductVersionStatusNotFound(id=product_version_id)

    def get_all_versions_status(self):
        return model_query(models.ProductVersionStatus).all()

    def update_version_status(self, platform_id, product_version_id,
                              status, logs_location):

        product_version_status = self.get_version_status(platform_id,
                                                         product_version_id)
        product_version_status.status = status
        product_version_status.logs_location = logs_location
        product_version_status.save()

    def delete_version_status(self, platform_id, product_version_id):
        query = model_query(models.ProductVersionStatus).filter_by(
            platform_id=platform_id, product_version_id=product_version_id)
        query.delete()
