# -*- coding: utf-8 -*-
# Copyright 2018 Objectif Libre
#
#    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.
#
from oslo_config import cfg
from oslo_db.sqlalchemy import utils
from oslo_log import log

from cloudkitty import db
from cloudkitty.storage_state import migration
from cloudkitty.storage_state import models
from cloudkitty import tzutils


LOG = log.getLogger(__name__)


CONF = cfg.CONF
# NOTE(peschk_l): Required for defaults
CONF.import_opt('backend', 'cloudkitty.fetcher', 'fetcher')
CONF.import_opt('collector', 'cloudkitty.collector', 'collect')
CONF.import_opt('scope_key', 'cloudkitty.collector', 'collect')


class StateManager(object):
    """Class allowing state management in CloudKitty"""

    model = models.IdentifierState

    def get_all(self,
                identifier=None,
                fetcher=None,
                collector=None,
                scope_key=None,
                limit=100, offset=0):
        """Returns the state of all scopes.

        This function returns the state of all scopes with support for optional
        filters.

        :param identifier: optional scope identifiers to filter on
        :type identifier: list
        :param fetcher: optional scope fetchers to filter on
        :type fetcher: list
        :param collector: optional collectors to filter on
        :type collector: list
        :param fetcher: optional fetchers to filter on
        :type fetcher: list
        :param scope_key: optional scope_keys to filter on
        :type scope_key: list
        """
        session = db.get_session()
        session.begin()

        q = utils.model_query(self.model, session)
        if identifier:
            q = q.filter(self.model.identifier.in_(identifier))
        if fetcher:
            q = q.filter(self.model.fetcher.in_(fetcher))
        if collector:
            q = q.filter(self.model.collector.in_(collector))
        if scope_key:
            q = q.filter(self.model.scope_key.in_(scope_key))
        q = q.offset(offset).limit(limit)

        r = q.all()
        session.close()

        for item in r:
            item.state = tzutils.utc_to_local(item.state)

        return r

    def _get_db_item(self, session, identifier,
                     fetcher=None, collector=None, scope_key=None):
        fetcher = fetcher or CONF.fetcher.backend
        collector = collector or CONF.collect.collector
        scope_key = scope_key or CONF.collect.scope_key

        q = utils.model_query(self.model, session)
        r = q.filter(self.model.identifier == identifier). \
            filter(self.model.scope_key == scope_key). \
            filter(self.model.fetcher == fetcher). \
            filter(self.model.collector == collector). \
            first()

        # In case the identifier exists with empty columns, update them
        if not r:
            # NOTE(peschk_l): We must use == instead of 'is' because sqlalchemy
            # overloads this operator
            r = q.filter(self.model.identifier == identifier). \
                filter(self.model.scope_key == None). \
                filter(self.model.fetcher == None). \
                filter(self.model.collector == None). \
                first()  # noqa
            if r:
                r.scope_key = scope_key
                r.collector = collector
                r.fetcher = fetcher
                LOG.info('Updating identifier "{i}" with scope_key "{sk}", '
                         'collector "{c}" and fetcher "{f}"'.format(
                             i=identifier,
                             sk=scope_key,
                             c=collector,
                             f=fetcher))
                session.commit()
        return r

    def set_state(self, identifier, state,
                  fetcher=None, collector=None, scope_key=None):
        """Set the state of a scope.

        :param identifier: Identifier of the scope
        :type identifier: str
        :param state: state of the scope
        :type state: datetime.datetime
        :param fetcher: Fetcher associated to the scope
        :type fetcher: str
        :param collector: Collector associated to the scope
        :type collector: str
        :param scope_key: scope_key associated to the scope
        :type scope_key: str
        """
        state = tzutils.local_to_utc(state, naive=True)
        session = db.get_session()
        session.begin()
        r = self._get_db_item(
            session, identifier, fetcher, collector, scope_key)

        if r:
            if r.state != state:
                r.state = state
                session.commit()
        else:
            state_object = self.model(
                identifier=identifier,
                state=state,
                fetcher=fetcher,
                collector=collector,
                scope_key=scope_key,
            )
            session.add(state_object)
            session.commit()

        session.close()

    def get_state(self, identifier,
                  fetcher=None, collector=None, scope_key=None):
        """Get the state of a scope.

        :param identifier: Identifier of the scope
        :type identifier: str
        :param fetcher: Fetcher associated to the scope
        :type fetcher: str
        :param collector: Collector associated to the scope
        :type collector: str
        :param scope_key: scope_key associated to the scope
        :type scope_key: str
        :rtype: datetime.datetime
        """
        session = db.get_session()
        session.begin()
        r = self._get_db_item(
            session, identifier, fetcher, collector, scope_key)
        session.close()
        return tzutils.utc_to_local(r.state) if r else None

    def init(self):
        migration.upgrade('head')

    # This is made in order to stay compatible with legacy behavior but
    # shouldn't be used
    def get_tenants(self, begin=None, end=None):
        session = db.get_session()
        session.begin()
        q = utils.model_query(self.model, session)
        session.close()
        return [tenant.identifier for tenant in q]
