# -*- coding: utf-8 -*-
import datetime
import uuid
import threading
from cachetools import cached, TTLCache

import flask_sqlalchemy
import flask_migrate

from sqlalchemy import MetaData
from sqlalchemy.dialects.postgresql import UUID


db = flask_sqlalchemy.SQLAlchemy(metadata=MetaData())
migrate = flask_migrate.Migrate()

segmentations_table = db.Table(
    'segmentations',
    db.Column('provision_id', UUID(as_uuid=True), db.ForeignKey('provision.id')),
    db.Column('segment_id', UUID(as_uuid=True), db.ForeignKey('segment.id'))
)


class BaseModel(object):
    def update(self, **kwargs):
        for key, value in kwargs.items():
            if key in self.__dict__:
                setattr(self, key, value)

    def as_dict(self):
        result = {}
        for key, value in self.__dict__:
            if not key.startswith('_'):
                result[key] = value
        return result


class Unit(BaseModel, db.Model):
    __tablename__ = 'resource_unit'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    key = db.Column(db.String, unique=True)
    base = db.Column(db.Integer)
    power = db.Column(db.Integer)


class Author(BaseModel, db.Model):
    __tablename__ = 'author'
    passport_uid = db.Column(db.String, unique=True, primary_key=True)
    staff_login = db.Column(db.String)
    operations = db.relationship('Operation', backref='author', lazy='joined')

    def as_dict(self):
        result = {
            'passportUid': self.passport_uid,
            'staffLogin': self.staff_login
        }
        return result


class Operation(BaseModel, db.Model):
    __tablename__ = 'operation'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    account = db.relationship('Account', backref='operation', lazy='joined')
    provision = db.relationship('Provision', backref='operation', lazy='joined')
    timestamp = db.Column(db.Integer, default=datetime.datetime.now().strftime('%s'))
    author_id = db.Column(db.String, db.ForeignKey('author.passport_uid'))

    def as_dict(self):
        result = {
            'operationId': str(self.id),
            'timestamp': self.timestamp,
            'author': self.author.as_dict(),
        }
        return result


class Provision(BaseModel, db.Model):
    __tablename__ = 'provision'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    account_id = db.Column(UUID(as_uuid=True), db.ForeignKey('account.id'))
    resource_key = db.Column(db.String, db.ForeignKey('resource_type.key'))
    segmentation = db.relationship('Segment', secondary=segmentations_table, uselist=False, lazy='joined')
    provided_amount = db.Column(db.Integer, nullable=False, default=0)
    allocated_amount = db.Column(db.Integer, nullable=False, default=0)
    # By default we expect same unit in provided and allocated provisions
    unit_key = db.Column(db.String, db.ForeignKey('resource_unit.key'))
    operation_id = db.Column(UUID(as_uuid=True), db.ForeignKey('operation.id'))
    quota_version = db.Column(db.Integer, default=1, autoincrement=True)

    def get_allocated_amount(self):
        ret = 0
        for nanny_instance in db.session.query(NannyService).filter_by(account_id=self.account_id).all():
            for resource_instance in db.session.query(Resource).filter_by(nanny_service_id=nanny_instance.id,
                                                                          key=self.resource_key,
                                                                          segment_id=self.segmentation.id).all():
                ret += resource_instance.amount
        return ret

    def as_dict(self):
        result = {
            'providedAmount': self.provided_amount,
            'providedAmountUnitKey': self.unit_key,
            'allocatedAmount': self.allocated_amount,
            'allocatedAmountUnitKey': self.unit_key,
            'quotaVersion': self.quota_version,
            'lastUpdate': self.operation.as_dict() if self.operation else {},
            'resourceKey': {
                'resourceTypeKey': self.resource_key,
                'segmentation': [self.segmentation.as_dict()]
            }
        }
        return result

    def as_quota_dict(self):
        return {
            'resource': self.resource_key,
            'values': {
                'location': self.segmentation.key,
                'amount': self.provided_amount
            }
        }


class Account(BaseModel, db.Model):
    __tablename__ = 'account'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    key = db.Column(db.String, nullable=False, unique=True)
    folder_id = db.Column(UUID(as_uuid=True), nullable=False)
    abc_service_id = db.Column(db.Integer, nullable=False, unique=True)
    display_name = db.Column(db.String, nullable=True)
    free_tier = db.Column(db.Boolean, default=False)
    deleted = db.Column(db.Boolean, default=False)
    operation_id = db.Column(UUID(as_uuid=True), db.ForeignKey('operation.id'))
    provisions = db.relationship('Provision', backref='account', lazy='joined')
    version = db.Column(db.Integer, default=1, autoincrement=True)

    def as_dict(self, with_provisions=False, deleted=False):
        result = {
            'accountId': str(self.id),
            'key': self.key,
            'displayName': self.display_name,
            'folderId': self.folder_id,
            'deleted': self.deleted,
            'lastUpdate': self.operation.as_dict() if self.operation else {},
            'freeTier':  self.free_tier,
            'accountVersion': self.version
        }
        if with_provisions:
            result['provisions'] = [provision.as_dict() for provision in self.provisions]
        if deleted:
            result['deleted'] = self.deleted
        return result

    def as_quota_dict(self):
        return {
            "quota_abc_id": self.abc_service_id,
            "quota_abc_name": self.display_name,
            "resources": [resource.as_quota_dict() for resource in self.provisions]
        }


class ResourceType(BaseModel, db.Model):
    __tablename__ = 'resource_type'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    key = db.Column(db.String, unique=True)
    unit = db.Column(db.String, db.ForeignKey('resource_unit.key'))


class Segment(BaseModel, db.Model):
    __tablename__ = 'segment'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    segmentation = db.Column(db.String, nullable=False)
    key = db.Column(db.String, unique=True)

    def as_dict(self):
        result = {
            'segmentationKey': self.segmentation,
            'segmentKey': self.key
        }
        return result


# SaaS Specific models

class SaasService(BaseModel, db.Model):
    __tablename__ = 'saas_service'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = db.Column(db.String)
    ctype = db.Column(db.String)
    owner = db.Column(db.String)
    account_id = db.Column(UUID(as_uuid=True), db.ForeignKey('account.id'))
    nanny_service = db.relationship('NannyService', backref='saas_service', lazy='joined')
    abc_service_quota = db.Column(db.Integer)
    abc_service_user = db.Column(db.Integer)
    abc_name = db.Column(db.String)
    abc_hr_name = db.Column(db.String)
    abc_quota_name = db.Column(db.String)
    abc_quota_hr_name = db.Column(db.String)

    def as_dict(self):
        return {
            'name': self.name,
            'ctype': self.ctype,
            'owner': self.owner,
            'abc_id': self.abc_service_user,
            'abc_name': self.abc_name,
            'abc_hr_name': self.abc_hr_name,
            'abc_quota_id': self.abc_service_quota,
            'abc_quota_name': self.abc_quota_name,
            'abc_quota_hr_name': self.abc_quota_hr_name,
            'nanny_services': [ns.as_dict() for ns in self.nanny_service],
        }


class NannyService(BaseModel, db.Model):
    __tablename__ = 'nanny_service'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = db.Column(db.String)
    saas_service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('saas_service.id'))
    account_id = db.Column(UUID(as_uuid=True), db.ForeignKey('account.id'))
    resources = db.relationship('Resource', backref='nanny_service', lazy='joined')

    def as_dict(self):
        return {
            'name': self.name,
            'resources': [res.as_dict() for res in self.resources],
        }


class Resource(BaseModel, db.Model):
    __tablename__ = 'resource'
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    key = db.Column(db.String)
    nanny_service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('nanny_service.id'))
    segment_id = db.Column(UUID(as_uuid=True), db.ForeignKey('segment.id'))
    resource_type_id = db.Column(UUID(as_uuid=True), db.ForeignKey('resource_type.id'))
    amount = db.Column(db.Integer)

    @staticmethod
    @cached(cache=TTLCache(maxsize=16, ttl=60), lock=threading.Lock())
    def get_segment_key(segment_id):
        return db.session.query(Segment).filter_by(id=segment_id).one().key

    @staticmethod
    @cached(cache=TTLCache(maxsize=16, ttl=60), lock=threading.Lock())
    def get_unit(resource_type_id):
        return db.session.query(ResourceType).filter_by(id=resource_type_id).one().unit

    def as_dict(self):
        return {
            'key': self.key,
            'amount': self.amount,
            'segment': self.get_segment_key(self.segment_id),
            'unit': self.get_unit(self.resource_type_id),
        }
