# Copyright 2015 Huawei Technologies Co., Ltd.
# All Rights Reserved
#
#    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 sqlalchemy as sql
from sqlalchemy.dialects import mysql
from sqlalchemy.orm import relationship
from sqlalchemy import schema

from oslo_db.sqlalchemy import models
from oslo_utils import timeutils

from tricircle.db import core


def MediumText():
    return sql.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql')


# Resource Model
class Aggregate(core.ModelBase, core.DictBase, models.TimestampMixin):
    """Represents a cluster of hosts that exists in this zone."""
    __tablename__ = 'aggregates'
    attributes = ['id', 'name', 'created_at', 'updated_at']

    id = sql.Column(sql.Integer, primary_key=True)
    name = sql.Column(sql.String(255), unique=True)


class AggregateMetadata(core.ModelBase, core.DictBase, models.TimestampMixin):
    """Represents a metadata key/value pair for an aggregate."""
    __tablename__ = 'aggregate_metadata'
    __table_args__ = (
        sql.Index('aggregate_metadata_key_idx', 'key'),
        schema.UniqueConstraint(
            'aggregate_id', 'key',
            name='uniq_aggregate_metadata0aggregate_id0key'),
    )
    attributes = ['id', 'key', 'value', 'aggregate_id',
                  'created_at', 'updated_at']

    id = sql.Column(sql.Integer, primary_key=True)
    key = sql.Column(sql.String(255), nullable=False)
    value = sql.Column(sql.String(255), nullable=False)
    aggregate_id = sql.Column(sql.Integer,
                              sql.ForeignKey('aggregates.id'), nullable=False)


class InstanceTypes(core.ModelBase, core.DictBase, models.TimestampMixin):
    """Represents possible flavors for instances.

    Note: instance_type and flavor are synonyms and the term instance_type is
    deprecated and in the process of being removed.
    """
    __tablename__ = 'instance_types'
    attributes = ['id', 'name', 'memory_mb', 'vcpus', 'root_gb',
                  'ephemeral_gb', 'flavorid', 'swap', 'rxtx_factor',
                  'vcpu_weight', 'disabled', 'is_public', 'created_at',
                  'updated_at']

    # Internal only primary key/id
    id = sql.Column(sql.Integer, primary_key=True)
    name = sql.Column(sql.String(255), unique=True)
    memory_mb = sql.Column(sql.Integer, nullable=False)
    vcpus = sql.Column(sql.Integer, nullable=False)
    root_gb = sql.Column(sql.Integer)
    ephemeral_gb = sql.Column(sql.Integer)
    # Public facing id will be renamed public_id
    flavorid = sql.Column(sql.String(255), unique=True)
    swap = sql.Column(sql.Integer, nullable=False, default=0)
    rxtx_factor = sql.Column(sql.Float, default=1)
    vcpu_weight = sql.Column(sql.Integer)
    disabled = sql.Column(sql.Boolean, default=False)
    is_public = sql.Column(sql.Boolean, default=True)


class InstanceTypeProjects(core.ModelBase, core.DictBase,
                           models.TimestampMixin):
    """Represent projects associated instance_types."""
    __tablename__ = 'instance_type_projects'
    __table_args__ = (schema.UniqueConstraint(
        'instance_type_id', 'project_id',
        name='uniq_instance_type_projects0instance_type_id0project_id'),
    )
    attributes = ['id', 'instance_type_id', 'project_id', 'created_at',
                  'updated_at']

    id = sql.Column(sql.Integer, primary_key=True)
    instance_type_id = sql.Column(sql.Integer,
                                  sql.ForeignKey('instance_types.id'),
                                  nullable=False)
    project_id = sql.Column(sql.String(255))


class InstanceTypeExtraSpecs(core.ModelBase, core.DictBase,
                             models.TimestampMixin):
    """Represents additional specs as key/value pairs for an instance_type."""
    __tablename__ = 'instance_type_extra_specs'
    __table_args__ = (
        sql.Index('instance_type_extra_specs_instance_type_id_key_idx',
                  'instance_type_id', 'key'),
        schema.UniqueConstraint(
            'instance_type_id', 'key',
            name='uniq_instance_type_extra_specs0instance_type_id0key'),
        {'mysql_collate': 'utf8_bin'},
    )
    attributes = ['id', 'key', 'value', 'instance_type_id', 'created_at',
                  'updated_at']

    id = sql.Column(sql.Integer, primary_key=True)
    key = sql.Column(sql.String(255))
    value = sql.Column(sql.String(255))
    instance_type_id = sql.Column(sql.Integer,
                                  sql.ForeignKey('instance_types.id'),
                                  nullable=False)


class KeyPair(core.ModelBase, core.DictBase, models.TimestampMixin):
    """Represents a public key pair for ssh / WinRM."""
    __tablename__ = 'key_pairs'
    __table_args__ = (
        schema.UniqueConstraint('user_id', 'name',
                                name='uniq_key_pairs0user_id0name'),
    )
    attributes = ['id', 'name', 'user_id', 'fingerprint', 'public_key', 'type',
                  'created_at', 'updated_at']

    id = sql.Column(sql.Integer, primary_key=True, nullable=False)
    name = sql.Column(sql.String(255), nullable=False)
    user_id = sql.Column(sql.String(255))
    fingerprint = sql.Column(sql.String(255))
    public_key = sql.Column(MediumText())
    type = sql.Column(sql.Enum('ssh', 'x509', name='keypair_types'),
                      nullable=False, server_default='ssh')


# Quota part are ported from Cinder for hierarchy multi-tenancy quota control
class QuotasBase(models.ModelBase, core.DictBase,
                 models.TimestampMixin, models.SoftDeleteMixin):
    """QuotasBase.

    provide base class for quota series tables. For it inherits from
    models.ModelBase, this is different from other tables
    """
    __table_args__ = {'mysql_engine': 'InnoDB'}

    metadata = None

    def delete(self, session):
        """Delete this object."""
        self.deleted = True
        self.deleted_at = timeutils.utcnow()
        self.save(session=session)


class Quotas(core.ModelBase, QuotasBase):
    """Represents a single quota override for a project.

    If there is no row for a given project id and resource, then the
    default for the quota class is used.  If there is no row for a
    given quota class and resource, then the default for the
    deployment is used. If the row is present but the hard limit is
    Null, then the resource is unlimited.
    """
    __tablename__ = 'quotas'
    __table_args__ = ()
    attributes = ['id', 'project_id', 'resource',
                  'hard_limit', 'allocated',
                  'created_at', 'updated_at', 'deleted_at', 'deleted']

    id = sql.Column(sql.Integer, primary_key=True)
    project_id = sql.Column(sql.String(255), index=True)
    resource = sql.Column(sql.String(255), nullable=False)
    hard_limit = sql.Column(sql.Integer)
    allocated = sql.Column(sql.Integer, default=0)


class QuotaClasses(core.ModelBase, QuotasBase):
    """Quota_classes.

    make default quota as a update-able quota.Mainly for the command
    quota-class-show and quota-class-update
    """
    __tablename__ = 'quota_classes'
    __table_args__ = ()
    attributes = ['id', 'class_name', 'resource', 'hard_limit',
                  'created_at', 'updated_at', 'deleted_at', 'deleted']

    id = sql.Column(sql.Integer, primary_key=True)
    class_name = sql.Column(sql.String(255), index=True)
    resource = sql.Column(sql.String(255), nullable=False)
    hard_limit = sql.Column(sql.Integer)


class QuotaUsages(core.ModelBase, QuotasBase):
    """Quota_uages.

    store quota usages for project resource
    """
    __tablename__ = 'quota_usages'
    __table_args__ = ()
    attributes = ['id', 'project_id', 'user_id', 'resource',
                  'in_use', 'reserved', 'until_refresh',
                  'created_at', 'updated_at', 'deleted_at', 'deleted']

    id = sql.Column(sql.Integer, primary_key=True)
    project_id = sql.Column(sql.String(255), index=True)
    user_id = sql.Column(sql.String(255), index=True)
    resource = sql.Column(sql.String(255), nullable=False)

    in_use = sql.Column(sql.Integer)
    reserved = sql.Column(sql.Integer, default=0)

    until_refresh = sql.Column(sql.Integer, default=0)

    @property
    def total(self):
        return self.in_use + self.reserved


class Reservation(core.ModelBase, QuotasBase):
    """Reservation.

    Represents a resource reservation for quotas
    """
    __tablename__ = 'reservations'
    __table_args__ = ()
    attributes = ['id', 'uuid', 'usage_id', 'project_id', 'resource',
                  'delta', 'expire',
                  'created_at', 'updated_at', 'deleted_at', 'deleted']

    id = sql.Column(sql.Integer, primary_key=True)
    uuid = sql.Column(sql.String(36), nullable=False)

    usage_id = sql.Column(sql.Integer,
                          sql.ForeignKey('quota_usages.id'),
                          nullable=False)

    project_id = sql.Column(sql.String(255), index=True)
    resource = sql.Column(sql.String(255))

    delta = sql.Column(sql.Integer)
    expire = sql.Column(sql.DateTime, nullable=False)

    usage = relationship(
        "QuotaUsages",
        foreign_keys=usage_id,
        primaryjoin='and_(Reservation.usage_id == QuotaUsages.id,'
                    'QuotaUsages.deleted == 0)')


class VolumeTypes(core.ModelBase, core.DictBase, models.TimestampMixin):
    """Represent possible volume_types of volumes offered."""
    __tablename__ = "volume_types"
    attributes = ['id', 'name', 'description', 'qos_specs_id', 'is_public',
                  'created_at', 'updated_at']

    id = sql.Column(sql.String(36), primary_key=True)
    name = sql.Column(sql.String(255), unique=True)
    description = sql.Column(sql.String(255))
    # A reference to qos_specs entity
    qos_specs_id = sql.Column(sql.String(36),
                              sql.ForeignKey('quality_of_service_specs.id'))
    is_public = sql.Column(sql.Boolean, default=True)


class QualityOfServiceSpecs(core.ModelBase, core.DictBase,
                            models.TimestampMixin):
    """Represents QoS specs as key/value pairs.

    QoS specs is standalone entity that can be associated/disassociated
    with volume types (one to many relation).  Adjacency list relationship
    pattern is used in this model in order to represent following hierarchical
    data with in flat table, e.g, following structure

    qos-specs-1  'Rate-Limit'
         |
         +------>  consumer = 'front-end'
         +------>  total_bytes_sec = 1048576
         +------>  total_iops_sec = 500

    qos-specs-2  'QoS_Level1'
         |
         +------>  consumer = 'back-end'
         +------>  max-iops =  1000
         +------>  min-iops = 200

    is represented by:

      id       specs_id       key                  value
    ------     --------   -------------            -----
    UUID-1     NULL       QoSSpec_Name           Rate-Limit
    UUID-2     UUID-1       consumer             front-end
    UUID-3     UUID-1     total_bytes_sec        1048576
    UUID-4     UUID-1     total_iops_sec           500
    UUID-5     NULL       QoSSpec_Name           QoS_Level1
    UUID-6     UUID-5       consumer             back-end
    UUID-7     UUID-5       max-iops               1000
    UUID-8     UUID-5       min-iops               200
    """
    __tablename__ = 'quality_of_service_specs'
    attributes = ['id', 'specs_id', 'key', 'value', 'created_at', 'updated_at']

    id = sql.Column(sql.String(36), primary_key=True)
    specs_id = sql.Column(sql.String(36), sql.ForeignKey(id))
    key = sql.Column(sql.String(255))
    value = sql.Column(sql.String(255))


# Pod Model
class Pod(core.ModelBase, core.DictBase):
    __tablename__ = 'cascaded_pods'
    attributes = ['pod_id', 'pod_name', 'pod_az_name', 'dc_name', 'az_name']

    pod_id = sql.Column('pod_id', sql.String(length=36), primary_key=True)
    pod_name = sql.Column('pod_name', sql.String(length=255), unique=True,
                          nullable=False)
    pod_az_name = sql.Column('pod_az_name', sql.String(length=255),
                             nullable=True)
    dc_name = sql.Column('dc_name', sql.String(length=255), nullable=True)
    az_name = sql.Column('az_name', sql.String(length=255), nullable=False)


class PodServiceConfiguration(core.ModelBase, core.DictBase):
    __tablename__ = 'cascaded_pod_service_configuration'
    attributes = ['service_id', 'pod_id', 'service_type', 'service_url']

    service_id = sql.Column('service_id', sql.String(length=64),
                            primary_key=True)
    pod_id = sql.Column('pod_id', sql.String(length=64),
                        sql.ForeignKey('cascaded_pods.pod_id'),
                        nullable=False)
    service_type = sql.Column('service_type', sql.String(length=64),
                              nullable=False)
    service_url = sql.Column('service_url', sql.String(length=512),
                             nullable=False)


# Tenant and pod binding model
class PodBinding(core.ModelBase, core.DictBase, models.TimestampMixin):
    __tablename__ = 'pod_binding'
    __table_args__ = (
        schema.UniqueConstraint(
            'tenant_id', 'pod_id',
            name='pod_binding0tenant_id0pod_id'),
    )
    attributes = ['id', 'tenant_id', 'pod_id',
                  'created_at', 'updated_at']

    id = sql.Column(sql.String(36), primary_key=True)
    tenant_id = sql.Column('tenant_id', sql.String(36), nullable=False)
    pod_id = sql.Column('pod_id', sql.String(36),
                        sql.ForeignKey('cascaded_pods.pod_id'),
                        nullable=False)


# Routing Model
class ResourceRouting(core.ModelBase, core.DictBase, models.TimestampMixin):
    __tablename__ = 'cascaded_pods_resource_routing'
    __table_args__ = (
        schema.UniqueConstraint(
            'top_id', 'pod_id',
            name='cascaded_pods_resource_routing0top_id0pod_id'),
    )
    attributes = ['id', 'top_id', 'bottom_id', 'pod_id', 'project_id',
                  'resource_type', 'created_at', 'updated_at']

    id = sql.Column('id', sql.Integer, primary_key=True)
    top_id = sql.Column('top_id', sql.String(length=127), nullable=False)
    bottom_id = sql.Column('bottom_id', sql.String(length=36))
    pod_id = sql.Column('pod_id', sql.String(length=64),
                        sql.ForeignKey('cascaded_pods.pod_id'),
                        nullable=False)
    project_id = sql.Column('project_id', sql.String(length=36))
    resource_type = sql.Column('resource_type', sql.String(length=64),
                               nullable=False)


class Job(core.ModelBase, core.DictBase):
    __tablename__ = 'job'
    __table_args__ = (
        schema.UniqueConstraint(
            'type', 'status', 'resource_id', 'extra_id',
            name='job0type0status0resource_id0extra_id'),
    )

    attributes = ['id', 'type', 'timestamp', 'status', 'resource_id',
                  'extra_id']

    id = sql.Column('id', sql.String(length=36), primary_key=True)
    type = sql.Column('type', sql.String(length=36))
    timestamp = sql.Column('timestamp', sql.TIMESTAMP,
                           server_default=sql.text('CURRENT_TIMESTAMP'))
    status = sql.Column('status', sql.String(length=36))
    resource_id = sql.Column('resource_id', sql.String(length=36))
    extra_id = sql.Column('extra_id', sql.String(length=36))
