import uuid
from sqlalchemy import UniqueConstraint, Column, Integer, Unicode, Numeric, Boolean, ForeignKey, func, DateTime, desc, PickleType
from sqlalchemy.orm import relationship
from brewman.models import Base, DBSession
from brewman.models.guidtype import GUID

__author__ = 'tanshu'


class Product(Base):
    __tablename__ = 'entities_products'
    __tableagrs__ = (UniqueConstraint('Name', 'Units'))

    id = Column('ProductID', GUID(), primary_key=True, default=uuid.uuid4)
    code = Column('Code', Integer, unique=True)
    name = Column('Name', Unicode(255), nullable=False)
    units = Column('Units', Unicode(255), nullable=False)
    fraction = Column('Fraction', Numeric, nullable=False)
    fraction_units = Column('FractionUnits', Unicode(255), nullable=False)
    product_yield = Column('ProductYield', Numeric, nullable=False)
    show_for_purchase = Column('ShowForPurchase', Boolean, nullable=False)
    product_group_id = Column('ProductGroupID', GUID(), ForeignKey('entities_productgroups.ProductGroupID'),
                              nullable=False)
    ledger_id = Column('LedgerID', GUID(), ForeignKey('entities_ledgers.LedgerID'), nullable=False)
    price = Column('Price', Numeric, nullable=False)
    discontinued = Column('Discontinued', Boolean, nullable=False)
    is_fixture = Column('IsFixture', Boolean, nullable=False)

    batches = relationship('Batch', backref='product')
    inventories = relationship('Inventory', backref='product')

    ledger = relationship('Ledger', primaryjoin="Ledger.id==Product.ledger_id", backref='products')

    def __init__(self, code=None, name=None, units=None, fraction=None, fraction_units=None, product_yield=None,
                 show_for_purchase=None, product_group_id=None, ledger_id=None, price=None, discontinued=None,
                 id=None,is_fixture=False):
        self.code = code
        self.name = name
        self.units = units
        self.fraction = fraction
        self.fraction_units = fraction_units
        self.product_yield = product_yield
        self.show_for_purchase = show_for_purchase
        self.product_group_id = product_group_id
        self.ledger_id = ledger_id
        self.price = price
        self.discontinued = discontinued
        self.id = id
        self.is_fixture = is_fixture

    @property
    def full_name(self):
        return "{0} ({1})".format(self.name, self.units)

    @classmethod
    def list(cls, name, active):
        query = DBSession.query(cls)
        if active is not None:
            query = query.filter(cls.discontinued != active)
        for item in name.split():
            query = query.filter(cls.name.ilike('%' + item + '%'))
        return query.order_by(cls.name)

    @classmethod
    def by_name(cls, name):
        return DBSession.query(cls).filter(cls.name == name).first()

    @classmethod
    def by_full_name(cls, full_name):
        return DBSession.query(cls).filter(cls.name + ' (' + cls.units + ')' == full_name).first()

    @classmethod
    def by_id(cls, id):
        return DBSession.query(cls).filter(cls.id == id).first()

    @classmethod
    def query(cls):
        return DBSession.query(cls)

    def create(self):
        code = DBSession.query(func.max(Product.code)).one()[0]
        if code is None:
            self.code = 1
        else:
            self.code = code + 1
        DBSession.add(self)
        return self

    def can_delete(self, advanced_delete):
        if self.is_fixture:
            return False, "{0} is a fixture and cannot be edited or deleted.".format(self.name)
        if not self.discontinued:
            return False, 'Product is active'
        if len(self.inventories) > 0 and not advanced_delete:
            return False, 'Product has entries'
        return True, ''

    @classmethod
    def suspense(cls):
        return uuid.UUID('aa79a643-9ddc-4790-ac7f-a41f9efb4c15')


class ProductGroup(Base):
    __tablename__ = 'entities_productgroups'

    id = Column('ProductGroupID', GUID(), primary_key=True, default=uuid.uuid4)
    name = Column('Name', Unicode(255), unique=True)
    is_fixture = Column('IsFixture', Boolean, nullable=False)

    products = relationship('Product', backref='product_group')

    def __init__(self, name=None, id=None, is_fixture=False):
        self.name = name
        self.id = id
        self.is_fixture = is_fixture

    @classmethod
    def list(cls):
        return DBSession.query(cls).order_by(cls.name)

    @classmethod
    def by_id(cls, id):
        return DBSession.query(cls).filter(cls.id == id).first()


class CostCenter(Base):
    __tablename__ = 'entities_costcenters'

    id = Column('CostCenterID', GUID(), primary_key=True, default=uuid.uuid4)
    name = Column('Name', Unicode(255), unique=True)
    is_fixture = Column('IsFixture', Boolean, nullable=False)

    ledgers = relationship('LedgerBase', backref='costcenter')
    journals = relationship('Journal', backref='costcenter')

    @property
    def __name__(self):
        return self.name

    def __init__(self, name=None, id=None, is_fixture=False):
        self.name = name
        self.id = id
        self.is_fixture = is_fixture

    @classmethod
    def by_id(cls, id):
        return DBSession.query(cls).filter(cls.id == id).first()

    @classmethod
    def list(cls):
        return DBSession.query(cls).order_by(cls.name).all()

    @classmethod
    def cost_center_purchase(cls):
        return uuid.UUID('7b845f95-dfef-fa4a-897c-f0baf15284a3')

    @classmethod
    def cost_center_kitchen(cls):
        return uuid.UUID('b2d398ce-e3cc-c542-9feb-5d7783e899df')

    @classmethod
    def cost_center_overall(cls):
        return uuid.UUID('36f59436-522a-0746-ae94-e0f746bf6c0d')

    @classmethod
    def overall(cls):
        return {'CostCenterID': uuid.UUID('36f59436-522a-0746-ae94-e0f746bf6c0d'), 'Name': 'Overall'}


class LedgerBase(Base):
    __tablename__ = 'entities_ledgers'

    id = Column('LedgerID', GUID(), primary_key=True, default=uuid.uuid4)
    code = Column('Code', Integer)
    name = Column('Name', Unicode(255), unique=True)
    type = Column('Type', Integer, nullable=False)
    ledger_type = Column('ledger_type', Unicode(50), nullable=False)
    is_active = Column('IsActive', Boolean)
    is_reconcilable = Column('IsReconcilable', Boolean)
    costcenter_id = Column('CostCenterID', GUID(), ForeignKey('entities_costcenters.CostCenterID'))
    is_fixture = Column('IsFixture', Boolean, nullable=False)

    __mapper_args__ = {'polymorphic_on': ledger_type}

    journals = relationship('Journal', backref='ledger')

    @property
    def __name__(self):
        return self.name

    @property
    def type_object(self):
        return LedgerType.by_id(self.type)

    def __init__(self, code=None, name=None, type=None, is_active=None, is_reconcilable=False, costcenter_id=None,
                 id=None, is_fixture=False):
        self.code = code
        self.name = name
        self.type = type
        self.is_active = is_active
        self.is_reconcilable = is_reconcilable
        self.costcenter_id = costcenter_id
        self.id = id
        self.is_fixture = is_fixture

    @classmethod
    def by_id(cls, id):
        if not isinstance(id, uuid.UUID):
            id = uuid.UUID(id)
        return DBSession.query(cls).filter(cls.id == id).first()

    @classmethod
    def by_name(cls, name):
        return DBSession.query(cls).filter(cls.name == name).first()

    @classmethod
    def list(cls):
        return DBSession.query(cls).all()

    @classmethod
    def list(cls, type, name, reconcilable=None, active=None):
        query = DBSession.query(cls)
        if type is not None:
            if not isinstance(type, int):
                type = int(type)
            query = query.filter(cls.type == type)
        if reconcilable is not None:
            query = query.filter(cls.is_reconcilable == reconcilable)
        if active is not None:
            query = query.filter(cls.is_active == active)
        if name is not None:
            for item in name.split():
                query = query.filter(cls.name.ilike('%' + item + '%'))
        return query.order_by(cls.name)

    def create(self):
        code = DBSession.query(func.max(LedgerBase.code)).filter(LedgerBase.type == self.type).one()[0]
        if code is None:
            self.code = 1
        else:
            self.code = code + 1
        DBSession.add(self)
        return self

    def can_delete(self, advanced_delete):
        if self.is_fixture:
            return False, "{0} is a fixture and cannot be edited or deleted.".format(self.name)
        if self.is_active:
            return False, 'Account is active'
        if len(self.journals) > 0 and not advanced_delete:
            return False, 'Account has journal entries'
        return True, ''

    @classmethod
    def get_code(cls, type):
        code = DBSession.query(func.max(LedgerBase.code)).filter(LedgerBase.type == type).one()[0]
        return 1 if code is None else code + 1


    @classmethod
    def all_purchases(cls):
        return uuid.UUID('240dd899-c413-854c-a7eb-67a29d154490')

    @classmethod
    def cash_in_hand(cls):
        return {'LedgerID': 'ed2341bb-80b8-9649-90db-f9aaca183bb3', 'Name': 'Cash in Hand'}

    @classmethod
    def local_purchase(cls):
        return {'LedgerID': 'd2b75912-505f-2548-9093-466dfff6a0f9', 'Name': 'Local Purchase'}

    @classmethod
    def salary(cls):
        return {'LedgerID': '5c2b54d0-c174-004d-a0d5-92cdaadcefa7', 'Name': 'Staff Salary'}

    @classmethod
    def esi_pf_expense(cls):
        return uuid.UUID('d2a1a286-e900-764b-a1a5-9f4b00dbb940')

    @classmethod
    def esi_pf_payable(cls):
        return uuid.UUID('42277912-cc18-854b-b134-9f4b00dba419')

    @classmethod
    def suspense(cls):
        return uuid.UUID('3854e317-6f3b-5142-ab26-9c44d4cddd08')


class Employee(LedgerBase):
    __tablename__ = 'entities_employees'
    __mapper_args__ = {'polymorphic_identity': 'employees'}

    id = Column('LedgerID', GUID(), ForeignKey(LedgerBase.id), primary_key=True)
    designation = Column('Designation', Unicode(255))
    salary = Column('Salary', Integer)
    service_points = Column('ServicePoints', Integer)
    joining_date = Column('JoiningDate', DateTime)
    leaving_date = Column('LeavingDate', DateTime)

    attendances = relationship('Attendance', backref='employee', cascade=None, cascade_backrefs=False)
    fingerprints = relationship('Fingerprint', backref='employee', cascade=None, cascade_backrefs=False)

    def __init__(self, code=None, name=None, is_active=None, costcenter_id=None, designation=None, salary=None,
                 service_points=None, joining_date=None, leaving_date=None):
        self.designation = designation
        self.salary = salary
        self.service_points = service_points
        self.joining_date = joining_date
        self.leaving_date = leaving_date
        super().__init__(code=code, name=name, type=10, is_active=is_active, is_reconcilable=False,
                         costcenter_id=costcenter_id)

    def create(self):
        code = DBSession.query(func.max(LedgerBase.code)).filter(LedgerBase.type == self.type).one()[0]
        if code is None:
            self.code = 1
        else:
            self.code = code + 1
        self.name += ' (' + str(self.code) + ')'
        DBSession.add(self)
        return self

    def can_delete(self, advanced_delete):
        return super(Employee, self).can_delete(advanced_delete)

    @classmethod
    def list(cls):
        return DBSession.query(Employee).order_by(desc(Employee.is_active)).order_by(Ledger.costcenter_id).order_by(
            Employee.designation).order_by(Employee.name).all()

    @classmethod
    def by_code(cls, code):
        return DBSession.query(cls).filter(cls.code == code).first()


class Ledger(LedgerBase):
    __mapper_args__ = {'polymorphic_identity': ''}

    def can_delete(self, advanced_delete):
        if len(self.products) > 0:
            return False, 'Account has products'
        return super(Ledger, self).can_delete(advanced_delete)

    @classmethod
    def list(cls):
        return DBSession.query(Ledger).order_by(Ledger.type).order_by(Ledger.name).order_by(Ledger.code).all()


class AttendanceType:
    def __init__(self, id, name, value=None):
        self.id = id
        self.name = name
        self.value = value

    @classmethod
    def list(cls):
        list = []
        list.append(AttendanceType(0, 'Not Set', 0))
        list.append(AttendanceType(1, 'Present', 1))
        list.append(AttendanceType(2, 'Off Day', 1))
        list.append(AttendanceType(3, 'On Leave', 0))
        list.append(AttendanceType(4, 'Absent', 0))
        list.append(AttendanceType(5, 'Half Day', .5))
        list.append(AttendanceType(6, 'Double Duty', 2))
        list.append(AttendanceType(7, 'Paid Leave Availed', 1))
        list.append(AttendanceType(8, 'Casual Leave Availed', 1))
        list.append(AttendanceType(9, 'Overtime', 0))
        list.append(AttendanceType(10, 'Worked on Off Day', 1))
        list.append(AttendanceType(11, 'Compensatory Off', 1))
        list.append(AttendanceType(12, 'Half Day + PL', 1))
        list.append(AttendanceType(13, 'Half Day + CL', 1))
        return list


    @classmethod
    def by_name(cls, name):
        list = cls.list()
        for item in list:
            if item.name == name:
                return item


    @classmethod
    def by_id(cls, id):
        list = cls.list()
        for item in list:
            if item.id == id:
                return item


class LedgerType:
    def __init__(self, id, name, balance_sheet=None, debit=None, is_current=None, order=None, show_in_list=None):
        self.id = id
        self.name = name
        self.balance_sheet = balance_sheet
        self.debit = debit
        self.is_current = is_current
        self.order = order
        self.show_in_list = show_in_list

    @classmethod
    def list(cls):
        list = []
        list.append(LedgerType(1, 'Cash', True, True, True, 10, True))
        list.append(LedgerType(2, 'Purchase', False, True, True, 20, True))
        list.append(LedgerType(3, 'Sale', False, False, True, 10, True))
        list.append(LedgerType(4, 'Assets', True, True, False, 20, True))
        list.append(LedgerType(5, 'Capital', True, False, False, 70, True))
        list.append(LedgerType(6, 'Debtors', True, True, True, 30, True))
        list.append(LedgerType(7, 'Expenses', False, True, True, 40, True))
        #        list.append(LedgerType(8, 'Discount', False, False, True, 30, True))
        list.append(LedgerType(9, 'Creditors', True, False, True, 60, True))
        list.append(LedgerType(10, 'Salary', True, True, True, 40, False))
        list.append(LedgerType(11, 'Liabilities', True, False, True, 50, True))
        list.append(LedgerType(12, 'Revenue', False, False, True, 30, True))
        #        list.append(LedgerType(13, 'Tax', True, False, True, 80, True))
        #        list.append(LedgerType(14, 'Total', False, False, False, 900, False))
        #        list.append(LedgerType(15, 'Net', False, False, False, 1000, False))
        return list


    @classmethod
    def by_name(cls, name):
        list = cls.list()
        for item in list:
            if item.name == name:
                return item


    @classmethod
    def by_id(cls, id):
        list = cls.list()
        for item in list:
            if item.id == id:
                return item


class DbSetting(Base):
    __tablename__ = 'settings'

    id = Column('SettingID', GUID(), primary_key=True, default=uuid.uuid4)
    name = Column('Name', Unicode(255), unique=True, nullable=False)
    data = Column('Data', PickleType)

    def __init__(self, id=None, name=None, data=None):
        self.id = id
        self.name = name
        self.data = data

    @classmethod
    def by_id(cls, id):
        if not isinstance(id, uuid.UUID):
            id = uuid.UUID(id)
        return DBSession.query(cls).filter(cls.id == id).first()

    @classmethod
    def by_name(cls, name):
        return DBSession.query(cls).filter(cls.name == name).first()

    @classmethod
    def list(cls):
        return DBSession.query(cls).order_by(cls.name).all()