from datetime import datetime
import uuid

from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import Column, Integer, Boolean, Unicode, DateTime, Numeric, ForeignKey
from sqlalchemy.orm import relationship, synonym, backref

from brewman.models.guidtype import GUID
from brewman.models import Base, DBSession
from brewman.models.master import Product


class VoucherType:
    def __init__(self, id, name):
        self.id = id
        self.name = name

    @classmethod
    def list(cls):
        list = []
        list.append(VoucherType(1, 'Journal'))
        list.append(VoucherType(2, 'Purchase'))
        list.append(VoucherType(3, 'Issue'))
        list.append(VoucherType(4, 'Payment'))
        list.append(VoucherType(5, 'Receipt'))
        list.append(VoucherType(6, 'Purchase Return'))
        list.append(VoucherType(7, 'Opening Ledgers'))
        list.append(VoucherType(8, 'Opening Batches'))
        list.append(VoucherType(9, 'Verification'))
        list.append(VoucherType(10, 'Opening Balance'))
        list.append(VoucherType(11, 'Closing Balance'))
        list.append(VoucherType(12, 'Salary Deduction'))
        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 Voucher(Base):
    __tablename__ = 'entities_vouchers'

    id = Column('VoucherID', GUID(), primary_key=True, default=uuid.uuid4)
    date = Column('Date', DateTime, nullable=False)
    reconcile_date = Column('ReconcileDate', DateTime, nullable=False)
    is_reconciled = Column('IsReconciled', Boolean, nullable=False)
    narration = Column('Narration', Unicode(1000), nullable=False)
    posted = Column('Posted', Boolean, nullable=False)
    creation_date = Column('CreationDate', DateTime(timezone=True), nullable=False)
    last_edit_date = Column('LastEditDate', DateTime(timezone=True), nullable=False)
    _type = Column('VoucherType', Integer, nullable=False)
    user_id = Column('UserID', GUID(), ForeignKey('auth_users.UserID'), nullable=False)
    poster_id = Column('PosterID', GUID(), ForeignKey('auth_users.UserID'))

    user = relationship('User', primaryjoin="User.id==Voucher.user_id", cascade=None)
    poster = relationship('User', primaryjoin="User.id==Voucher.poster_id", cascade=None)

    journals = relationship('Journal', backref='voucher', cascade="delete, delete-orphan", cascade_backrefs=False)
    inventories = relationship('Inventory', backref='voucher', cascade="delete, delete-orphan", cascade_backrefs=False)
    salary_deductions = relationship('SalaryDeduction', backref='voucher', cascade="delete, delete-orphan",
                                     cascade_backrefs=False)

    def _get_type(self):
        return self._type
        # for item in VoucherType.list():
        # if self._type == item.id:
        # return item

    def _set_type(self, value):
        if type(value) == int:
            self._type = value
        else:
            self._type = value.id

    type = property(_get_type, _set_type)
    type = synonym('_type', descriptor=type)

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

    def __init__(self, date=None, reconcile_date=None, narration='', posted=False, creation_date=None,
                 last_edit_date=None, type=None, user_id=None, poster_id=None, is_reconciled=False):
        self.date = date
        self.reconcile_date = reconcile_date if reconcile_date and is_reconciled else date
        self.narration = narration
        self.posted = posted
        self.creation_date = datetime.utcnow() if creation_date is None else creation_date
        self.last_edit_date = datetime.utcnow() if last_edit_date is None else last_edit_date
        self.type = type
        self.user_id = user_id
        self.poster_id = poster_id
        self.is_reconciled = is_reconciled

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

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

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


class Journal(Base):
    __tablename__ = 'entities_journals'

    id = Column('JournalID', GUID(), primary_key=True, default=uuid.uuid4)
    debit = Column('Debit', Integer)
    amount = Column('Amount', Numeric)
    voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID'), nullable=False)
    ledger_id = Column('LedgerID', GUID(), ForeignKey('entities_ledgers.LedgerID'), nullable=False)
    cost_center_id = Column('CostCenterID', GUID(), ForeignKey('entities_costcenters.CostCenterID'), nullable=False)

    @hybrid_property
    def signed_amount(self):
        return self.debit * self.amount

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

    def __init__(self, id=None, debit=None, amount=None, voucher_id=None, ledger_id=None, cost_center_id=None):
        self.id = id
        self.debit = debit
        self.amount = amount
        self.voucher_id = voucher_id
        self.ledger_id = ledger_id
        self.cost_center_id = cost_center_id

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

    @classmethod
    def list(cls, voucher_id):
        return DBSession.query(cls).filter(cls.voucher_id == voucher_id).all()

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


class SalaryDeduction(Base):
    __tablename__ = 'entities_salarydeductions'
    id = Column('SalaryDeductionID', GUID(), primary_key=True, default=uuid.uuid4)
    voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID'), nullable=False)
    journal_id = Column('JournalID', GUID(), ForeignKey('entities_journals.JournalID'), nullable=False)
    gross_salary = Column('GrossSalary', Integer)
    days_worked = Column('DaysWorked', Integer)
    esi_ee = Column('EsiEmployee', Integer)
    pf_ee = Column('PfEmployee', Integer)
    esi_er = Column('EsiEmployer', Integer)
    pf_er = Column('PfEmployer', Integer)

    journal = relationship(Journal, backref=backref('salary_deduction', uselist=False), cascade=None,
                           cascade_backrefs=False)

    def __init__(self, id=None, voucher_id=None, journal_id=None, journal=None, gross_salary=None, days_worked=None,
                 esi_ee=None, pf_ee=None, esi_er=None, pf_er=None):
        self.id = id
        self.voucher_id = voucher_id
        self.journal_id = journal_id
        self.gross_salary = gross_salary
        self.days_worked = days_worked
        self.esi_ee = esi_ee
        self.pf_ee = pf_ee
        self.esi_er = esi_er
        self.pf_er = pf_er
        if journal_id is None and journal is not None:
            self.journal = journal


class Inventory(Base):
    __tablename__ = 'entities_inventories'
    #        __tableagrs__ = (UniqueConstraint('VoucherID', 'ProductID'))
    id = Column('InventoryID', GUID(), primary_key=True, default=uuid.uuid4)
    voucher_id = Column('VoucherID', GUID(), ForeignKey('entities_vouchers.VoucherID'), nullable=False)
    product_id = Column('ProductID', GUID(), ForeignKey('entities_products.ProductID'), nullable=False)
    batch_id = Column('BatchID', GUID(), ForeignKey('entities_batches.BatchID'), nullable=False)
    quantity = Column('Quantity', Numeric)
    rate = Column('Rate', Numeric)
    tax = Column('Tax', Numeric)
    discount = Column('Discount', Numeric)

    def __init__(self, id=None, voucher_id=None, product_id=None, batch_id=None, quantity=None, rate=None,
                 tax=None, discount=None, batch=None):
        self.id = id
        self.voucher_id = voucher_id
        self.product_id = product_id
        self.batch_id = batch_id
        self.quantity = quantity
        self.rate = rate
        self.tax = tax
        self.discount = discount
        if batch_id is None and batch is not None:
            self.batch = batch

    @hybrid_property
    def amount(self):
        return self.quantity * self.rate * (1 + self.tax) * (1 - self.discount)


class Batch(Base):
    __tablename__ = 'entities_batches'

    id = Column('BatchID', GUID(), primary_key=True, default=uuid.uuid4)
    name = Column('Name', DateTime)
    product_id = Column('ProductID', GUID(), ForeignKey('entities_products.ProductID'), nullable=False)
    quantity_remaining = Column('QuantityRemaining', Numeric)
    rate = Column('Rate', Numeric)
    tax = Column('Tax', Numeric)
    discount = Column('Discount', Numeric)

    inventories = relationship('Inventory', backref='batch', cascade=None, cascade_backrefs=False)

    def __init__(self, name=None, product_id=None, quantity_remaining=None, rate=None, tax=None,
                 discount=None):
        self.name = name
        self.product_id = product_id
        self.quantity_remaining = quantity_remaining
        self.rate = rate
        self.tax = tax
        self.discount = discount

    def amount(self):
        return self.quantity_remaining * self.rate * (1 + self.tax) * (1 - self.discount)

    @classmethod
    def list(cls, name, include_nil, date):
        query = DBSession.query(Batch).join(Batch.product)
        if not include_nil:
            query = query.filter(cls.quantity_remaining > 0)
        if date is not None:
            query = query.filter(cls.name <= date)
        for item in name.split():
            query = query.filter(Product.name.ilike('%' + item + '%'))
        return query.order_by(Product.name).order_by(cls.name).all()


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

    @classmethod
    def suspense(cls):
        return uuid.UUID('a955790e-93cf-493c-a816-c7d92b127383')


class Attendance(Base):
    __tablename__ = 'entities_attendances'

    id = Column('AttendanceID', GUID(), primary_key=True, default=uuid.uuid4)
    employee_id = Column('EmployeeID', GUID(), ForeignKey('entities_employees.LedgerID'))
    date = Column('Date', DateTime)
    attendance_type = Column('AttendanceType', Integer)
    amount = Column('Amount', Numeric)
    creation_date = Column('CreationDate', DateTime(timezone=True))
    user_id = Column('UserID', GUID(), ForeignKey('auth_users.UserID'))
    is_valid = Column('IsValid', Boolean)

    user = relationship('User', primaryjoin="User.id==Attendance.user_id")

    def __init__(self, id=None, employee_id=None, date=None, attendance_type=None, amount=None, creation_date=None,
                 user_id=None, is_valid=None):
        self.id = id
        self.employee_id = employee_id
        self.date = date
        self.attendance_type = attendance_type
        self.amount = amount if amount is not None else 0
        self.creation_date = datetime.utcnow() if creation_date is None else creation_date
        self.user_id = user_id
        self.is_valid = is_valid if is_valid is not None else True

    @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):
        old = DBSession.query(Attendance).filter(Attendance.date == self.date) \
            .filter(Attendance.employee_id == self.employee_id) \
            .filter(Attendance.is_valid == True).first()
        if old is None or old.attendance_type != self.attendance_type:
            if old is not None:
                old.is_valid = False
            DBSession.add(self)


class Fingerprint(Base):
    __tablename__ = 'entities_fingerprints'

    id = Column('FingerprintID', GUID(), primary_key=True, default=uuid.uuid4)
    employee_id = Column('EmployeeID', GUID(), ForeignKey('entities_employees.LedgerID'))
    date = Column('Date', DateTime)

    def __init__(self, id=None, employee_id=None, date=None):
        self.id = id
        self.employee_id = employee_id
        self.date = date
        self.fingerprint_type = 0

    @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):
        old = DBSession.query(Fingerprint).filter(Fingerprint.date == self.date) \
            .filter(Fingerprint.employee_id == self.employee_id).first()
        if old is None:
            DBSession.add(self)
            return self
        else:
            return old

