from trytond.pool import Pool, PoolMeta
from trytond.model import ModelView, ModelSQL, fields
from trytond.modules.currency.fields import Monetary
from trytond.pyson import Eval, Bool, If, Get, Equal
from decimal import Decimal
from trytond.modules.product import price_digits
from trytond.transaction import Transaction
from trytond.model import Workflow
from trytond.modules.company.model import (
    employee_field, set_employee, reset_employee)

from trytond.exceptions import UserError

from trytond.wizard import (
    Button, StateAction, StateTransition, StateView, Wizard)


class Sale(metaclass=PoolMeta):
    'Sale'
    __name__ = 'sale.sale'

    quote_number = fields.Char("Quote Number", readonly=True)
    sale_type = fields.Selection([('maintenance', 'Maintenance'),
                                  ('equipments', 'Equipments'),
                                  ('replaces', 'Replaces')], "Sale Type", required=True,
                                 states={'readonly': Eval('state') != 'draft'})
    maintenance_type = fields.Selection([('', ""),
                                         ('preventive', 'Preventive'),
                                         ('corrective', 'Corrective')
                                         ], "Maintenance Type",
                                         states={
                                             'invisible': Eval('sale_type') != "maintenance",
                                             'required': Eval('sale_type') == "maintenance",
                                             'readonly': Eval('state') != 'draft'},
                                         depends=['sale_type'])

    contract_ref = fields.Reference("Contract Base", selection='get_origin_contract',
                                    #domain=[('party', '=', Eval('party')),
                                    #        ('state', '=', 'closed')],
                                    states={'invisible': (Eval('sale_type') != 'maintenance')},
                                    search_context={
                                       'related_party': Eval('party'),
                                    },)
    agended = fields.Boolean("Scheduling",states={
        'invisible': (Eval('sale_type') != 'maintenance'),
        'readonly': True})

    
    @classmethod
    def __setup__(cls):
        super(Sale, cls).__setup__()
        cls.contact.states['required']=True
        cls.description.states['required']=True
        cls.sale_date.states['required']=True
        cls.payment_term.states['required']=True
        cls._buttons.update({
            'draft': {
                'invisible': (Eval('state').in_(
                    ['cancelled', 'draft'])) | (Eval('shipment_state') == 'sent')}})
        
        cls._transitions |= set((
                ('draft', 'quotation'),
                ('quotation', 'confirmed'),
                ('confirmed', 'processing'),
                ('confirmed', 'draft'),
                ('processing', 'processing'),
                ('processing', 'done'),
                ('done', 'processing'),
                ('draft', 'cancelled'),
                ('quotation', 'cancelled'),
                ('quotation', 'draft'),
                ('cancelled', 'draft'),
                ('processing', 'draft')
                ))
        
    @fields.depends('lines', 'sale_type', 'agended')
    def on_chage_sale_type(self):
        self.lines= []
        if self.sale_type != "maintenance":
            self.agended = False
        elif self.sale_type == "maintenance":
            self.invoice_method = 'order'

    @classmethod
    def default_agended(self):
        return False
    
    @classmethod
    def _get_origin_contract(cls):
        'Return list of Model names for origin Reference'
        pool = Pool()
        Contract = pool.get('optical_equipment.contract')

        return [Contract.__name__]


    @classmethod
    def get_origin_contract(cls):
        Model = Pool().get('ir.model')
        get_name = Model.get_name
        models = cls._get_origin_contract() 

        return [(None, '')] + [(m, get_name(m)) for m in models]

    def _get_shipment_sale(self, Shipment, key):
        values = {
            'customer': self.shipment_party or self.party,
            'delivery_address': self.shipment_address,
            'company': self.company,
            'sale_type': self.sale_type,
            'service_maintenance_initial': True if self.sale_type != 'equipments' else False,
            }
        values.update(dict(key))

        return Shipment(**values)

    @classmethod
    def set_quote_number(cls, sales):
        '''
        Fill the number field with the sale sequence
        '''
        pool = Pool()
        Config = pool.get('optical_equipment.configuration')
        config = Config(1)
        for sale in sales:
            if config.equipment_sequence != None:
                if not sale.quote_number:
                    try:
                        sale.quote_number = config.sale_quote_number.get()
                        cls.save(sales)
                    except UserError:
                        raise UserError(str('Validation Error'))
            else:
                raise UserError(gettext('optical_equipment.msg_not_sequence_quote'))
        
    @classmethod
    def copy(cls, sales, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()

        default.setdefault('number', None)
        default.setdefault('invoice_state', 'none')
        default.setdefault('invoices_ignored', None)
        default.setdefault('moves', None)
        default.setdefault('shipment_state', 'none')
        default.setdefault('quoted_by')
        default.setdefault('confirmed_by')

        return super(Sale, cls).copy(sales, default=default)

    @classmethod
    @ModelView.button
    @Workflow.transition('quotation')
    def quote(cls, sales):
        pool = Pool()
        AdvancePaymentCondition = pool.get('sale.advance_payment.condition')
        for sale in sales:
            sale.check_for_quotation()
        cls.set_quote_number(sales)
                    
        for sale in sales:
            sale.set_advance_payment_term()
        cls.save(sales)
        
    @classmethod
    @ModelView.button
    @Workflow.transition('confirmed')
    @set_employee('confirmed_by')
    def confirm(cls, sales):
        pool = Pool()
        Configuration = pool.get('sale.configuration')
        transaction = Transaction()
        context = transaction.context
        cls.set_sale_date(sales)
        cls.store_cache(sales)
        config = Configuration(1)

        MaintenanceService = pool.get('optical_equipment_maintenance.service')
        for sale in sales:
            if sale.sale_type == 'maintenance':
                for line in sale.lines:
                    maintenanceService = MaintenanceService(
                        description=sale.description,
                        maintenance_type=sale.maintenance_type,
                        state_agended='no_agenda',
                        propietary=sale.party,
                        propietary_address=sale.shipment_address,
                        contract_origin=sale.contract_ref if sale.contract_ref else None,
                        sale_origin=sale,
                        sale_date=sale.sale_date,
                        state="draft"
                    )
                    maintenanceService.save()
                sale.agended = True
                sale.save()
                
        cls.set_number(sales)
        with transaction.set_context(
                queue_scheduled_at=config.sale_process_after,
                queue_batch=context.get('queue_batch', True)):
            cls.__queue__.process(sales)


class SaleLine(metaclass=PoolMeta):
    'SaleLine'
    __name__ = 'sale.line'

    product_equipment = fields.Boolean("Product Equipment")
    #equipment = fields.Many2One('optical_equipment.equipment', "Equipment",
    #                            domain=[('state', '=', 'registred'),
    #                                    ('product','=', Eval('product'))
    #                                    ],
    #                            states={'invisible': If(~Eval('product_equipment'), True)},)
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
        'on_change_with_unit_digits')

    
    @classmethod
    def __setup__(cls):
        super(SaleLine, cls).__setup__()
        cls.product.domain.append(
            If(Eval('_parent_sale.sale_type') == 'maintenance',
               [('type', '=', 'service'),
                ('maintenance_activity', '=', True)], []))
        cls.product.domain.append(If(Eval('_parent_sale.sale_type') == 'replaces',
               [('replacement', '=', True)], []))

        
    def on_change_with_unit_digits(self, name=None):
        if self.unit:
            return self.unit.digits
        return 2
    

    @fields.depends('product', 'unit', 'quantity', 'sale',
                    '_parent_sale.party', '_parent_sale.sale_type', methods=['_get_tax_rule_pattern',
    '_get_context_sale_price','on_change_with_amount'])
    def on_change_product(self):
        Product = Pool().get('product.product')            
        if not self.product:
            self.product_equipment = False
            self.unit = None
            self.quantity = None
            return

        else:
            party = None

            if self.sale.sale_type == 'equipments':
                self.quantity = 1
                
            if self.sale and self.sale.party:
                self.product_equipment = False
                party = self.sale.party

            # Set taxes before unit_price to have taxes in context of sale price
            taxes = []
            pattern = self._get_tax_rule_pattern()
            for tax in self.product.customer_taxes_used:
                if party and party.customer_tax_rule:
                    tax_ids = party.customer_tax_rule.apply(tax, pattern)
                    if tax_ids:
                        taxes.extend(tax_ids)
                    continue
                taxes.append(tax.id)

            if party and party.customer_tax_rule:
                tax_ids = party.customer_tax_rule.apply(None, pattern)
                if tax_ids:
                    taxes.extend(tax_ids)
            self.taxes = taxes

            category = self.product.sale_uom.category
            if not self.unit or self.unit.category != category:
                self.unit = self.product.sale_uom
                self.unit_digits = self.product.sale_uom.digits

            with Transaction().set_context(self._get_context_sale_price()):
                self.unit_price = Product.get_sale_price([self.product],
                        self.quantity or 0)[self.product.id]

                if self.unit_price:
                    self.unit_price = self.unit_price.quantize(
                        Decimal(1) / 10 ** self.__class__.unit_price.digits[1])

            self.type = 'line'
            self.amount = self.on_change_with_amount()

            if self.product.equipment:
                self.product_equipment = True

    def get_move(self, shipment_type):
        '''
        Return moves for the sale line according to shipment_type
        '''

        pool = Pool()
        Move = pool.get('stock.move')

        if self.type != 'line':
            return

        if not self.product:
            return

        if self.product.type not in Move.get_product_types():
            return

        if (shipment_type == 'out') != (self.quantity >= 0):
            return


        quantity = (self._get_move_quantity(shipment_type)
            - self._get_shipped_quantity(shipment_type))

        quantity = self.unit.round(quantity)

        if quantity <= 0:
            return

        if not self.sale.party.customer_location:
            raise PartyLocationError(
                gettext('sale.msg_sale_customer_location_required',
                    sale=self.sale.rec_name,
                    party=self.sale.party.rec_name))

        move = Move()
        move.quantity = quantity
        move.uom = self.unit
        move.product = self.product
        move.from_location = self.from_location
        move.to_location = self.to_location
        move.state = 'draft'
        move.company = self.sale.company

        if move.on_change_with_unit_price_required():
            move.unit_price = self.unit_price
            move.currency = self.sale.currency
            
        move.planned_date = self.planned_shipping_date
        move.invoice_lines = self._get_move_invoice_lines(shipment_type)
        move.origin = self

        return move