# This file is part of lims_analysis_sheet_stock module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields, Unique
from trytond.wizard import Wizard, StateTransition, StateView, Button
from trytond.pyson import Eval, Id
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.modules.lims_interface.interface import FUNCTIONS
from .function import custom_functions

FUNCTIONS.update(custom_functions)


class TemplateAnalysisSheet(metaclass=PoolMeta):
    __name__ = 'lims.template.analysis_sheet'

    materials = fields.One2Many('lims.template.analysis_sheet.material',
        'template', 'Materials')


class TemplateAnalysisSheetMaterial(ModelSQL, ModelView):
    'Template Material'
    __name__ = 'lims.template.analysis_sheet.material'

    template = fields.Many2One('lims.template.analysis_sheet', 'Template',
        required=True, ondelete='CASCADE', select=True)
    product = fields.Many2One('product.product', 'Product',
        required=True, domain=[
            ('type', '!=', 'service'),
        ])
    uom_category = fields.Function(fields.Many2One(
        'product.uom.category', 'Uom Category'), 'on_change_with_uom_category')
    uom = fields.Many2One('product.uom', 'Uom', required=True,
        domain=[
            ('category', '=', Eval('uom_category')),
        ], depends=['uom_category'])
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
        'on_change_with_unit_digits')
    quantity = fields.Float('Quantity', required=True,
        domain=['OR',
            ('quantity', '>=', 0),
            ('quantity', '=', None),
            ],
        digits=(16, Eval('unit_digits', 2)),
        depends=['unit_digits'])
    quantity_by_sample = fields.Boolean('Quantity by Sample')
    interface = fields.Function(fields.Many2One(
        'lims.interface', 'Device Interface'), 'get_interface')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        t = cls.__table__()
        cls._sql_constraints = [
            ('product_template_uniq', Unique(t, t.product, t.template),
                'lims_analysis_sheet_stock.msg_product_template_unique'),
            ]

    @fields.depends('product', 'uom')
    def on_change_product(self):
        if self.product:
            category = self.product.default_uom.category
            if not self.uom or self.uom.category != category:
                self.uom = self.product.default_uom
                self.unit_digits = self.product.default_uom.digits
        else:
            self.uom = None

    @fields.depends('product')
    def on_change_with_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom.category.id

    @fields.depends('uom')
    def on_change_with_unit_digits(self, name=None):
        if self.uom:
            return self.uom.digits
        return 2

    def get_rec_name(self, name):
        return self.product.rec_name

    @classmethod
    def search_rec_name(cls, name, clause):
        return [('product.rec_name',) + tuple(clause[1:])]

    def compute_quantity(self, factor):
        return self.uom.ceil(self.quantity * factor)

    def get_interface(self, name):
        return self.template.interface.id


class AnalysisSheet(metaclass=PoolMeta):
    __name__ = 'lims.analysis_sheet'

    moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)

    @classmethod
    @ModelView.button
    def validate_(cls, sheets):
        super().validate_(sheets)
        cls.check_materials(sheets)

    @classmethod
    def check_materials(cls, sheets):
        for s in sheets:
            if s.template.materials and not s.moves:
                raise UserError(gettext(
                    'lims_analysis_sheet_stock.msg_sheet_not_materials'))


class AddMaterialStart(ModelView):
    'Add Material'
    __name__ = 'lims.analysis_sheet.add_material.start'

    materials = fields.One2Many(
        'lims.analysis_sheet.add_material_detail.start',
        'material', 'Materials')


class AddMaterialDetailStart(ModelView):
    'Add Material'
    __name__ = 'lims.analysis_sheet.add_material_detail.start'

    material = fields.Many2One('lims.analysis_sheet.add_material.start',
        'Material')
    product = fields.Many2One('product.product', 'Product',
        required=True, domain=[
            ('type', '!=', 'service'),
        ])
    lot = fields.Many2One('stock.lot', 'Lot',
        domain=[
            ('product', '=', Eval('product')),
            ],
        depends=['product'])
    from_location = fields.Many2One('stock.location', 'From Location',
        domain=[('type', '=', 'storage')])
    uom_category = fields.Function(fields.Many2One(
        'product.uom.category', 'Uom Category'), 'on_change_with_uom_category')
    uom = fields.Many2One('product.uom', 'Uom', required=True,
        domain=[
            ('category', '=', Eval('uom_category')),
        ], depends=['uom_category'])
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
        'on_change_with_unit_digits')
    quantity = fields.Float('Quantity', required=True,
        domain=['OR',
            ('quantity', '>=', 0),
            ('quantity', '=', None),
            ],
        digits=(16, Eval('unit_digits', 2)),
        depends=['unit_digits'])

    @fields.depends('product', 'uom')
    def on_change_product(self):
        if self.product:
            category = self.product.default_uom.category
            if not self.uom or self.uom.category != category:
                self.uom = self.product.default_uom
                self.unit_digits = self.product.default_uom.digits
        else:
            self.uom = None

    @fields.depends('product')
    def on_change_with_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom.category.id

    @fields.depends('uom')
    def on_change_with_unit_digits(self, name=None):
        if self.uom:
            return self.uom.digits
        return 2


class AddMaterialAssignFailed(ModelView):
    'Add Material Assign Failed'
    __name__ = 'lims.analysis_sheet.add_material.assign.failed'

    inventory_moves = fields.Many2Many('stock.move', None, None,
        'Inventory Moves', readonly=True)

    @staticmethod
    def default_inventory_moves():
        AnalysisSheet = Pool().get('lims.analysis_sheet')

        sheet = None
        line_id = Transaction().context.get('active_id', None)
        sheet_id = Transaction().context.get('lims_analysis_sheet', None)
        if line_id and sheet_id:
            sheet = AnalysisSheet(sheet_id)
        if not sheet:
            return []
        return [x.id for x in sheet.moves if x.state == 'draft']


class AddMaterial(Wizard):
    'Add Material'
    __name__ = 'lims.analysis_sheet.add_material'

    start_state = 'check'
    check = StateTransition()
    start = StateView('lims.analysis_sheet.add_material.start',
        'lims_analysis_sheet_stock.add_material_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Save', 'save', 'tryton-save', default=True),
            ])
    save = StateTransition()
    failed = StateView('lims.analysis_sheet.add_material.assign.failed',
        'lims_analysis_sheet_stock.add_material_assign_failed_view_form', [
            Button('Force Assign', 'force', 'tryton-forward',
                states={
                    'invisible': ~Id('stock',
                        'group_stock_force_assignment').in_(
                        Eval('context', {}).get('groups', [])),
                    }),
            Button('OK', 'delete_moves', 'tryton-ok', True),
            ])
    force = StateTransition()
    delete_moves = StateTransition()

    def _get_analysis_sheet_id(self):
        return Transaction().context.get('lims_analysis_sheet', None)

    def transition_check(self):
        AnalysisSheet = Pool().get('lims.analysis_sheet')

        line_id = Transaction().context.get('active_id', None)
        sheet_id = self._get_analysis_sheet_id()
        if line_id and sheet_id:
            sheet = AnalysisSheet(sheet_id)
            if sheet.state == 'active':
                return 'start'

        return 'end'

    def default_start(self, fields):
        pool = Pool()
        AnalysisSheet = pool.get('lims.analysis_sheet')

        sheet_id = self._get_analysis_sheet_id()
        sheet = AnalysisSheet(sheet_id)

        moves = []
        for material in sheet.template.materials:
            move = {
                'product': material.product.id,
                'uom': material.uom.id,
                'quantity': material.quantity,
                }

            moves.append(move)

        defaults = {
            'materials': moves,
            }
        return defaults

    def _move(self, origin, from_location, to_location, product, uom, quantity,
            lot):
        Move = Pool().get('stock.move')
        move = Move(
            origin=origin,
            product=product,
            lot=lot,
            uom=uom,
            quantity=quantity,
            from_location=from_location,
            to_location=to_location,
            state='draft',
            )
        return move

    def get_moves(self):
        pool = Pool()
        AnalysisSheet = pool.get('lims.analysis_sheet')
        Move = pool.get('stock.move')
        Config = Pool().get('lims.configuration')

        config = Config(1)
        sheet_id = self._get_analysis_sheet_id()
        sheet = AnalysisSheet(sheet_id)

        moves = []
        for material in self.start.materials:
            move = self._move(sheet, material.from_location,
                config.materials_consumption_location, material.product,
                material.uom, material.quantity, material.lot)
            moves.append(move)

        Move.save(moves)
        return moves

    def transition_save(self):
        pool = Pool()
        Move = pool.get('stock.move')

        moves = self.get_moves()

        if not Move.assign_try(moves):
            return 'failed'
        Move.do(moves)

        return 'end'

    def transition_force(self):
        pool = Pool()
        AnalysisSheet = pool.get('lims.analysis_sheet')
        Move = pool.get('stock.move')

        sheet = None
        line_id = Transaction().context.get('active_id', None)
        sheet_id = Transaction().context.get('lims_analysis_sheet', None)
        if line_id and sheet_id:
            sheet = AnalysisSheet(sheet_id)
        Move.do([m for m in sheet.moves])

        return 'end'

    def transition_delete_moves(self):
        pool = Pool()
        AnalysisSheet = pool.get('lims.analysis_sheet')
        Move = pool.get('stock.move')

        sheet = None
        line_id = Transaction().context.get('active_id', None)
        sheet_id = Transaction().context.get('lims_analysis_sheet', None)
        if line_id and sheet_id:
            sheet = AnalysisSheet(sheet_id)

        Move.delete([m for m in sheet.moves])

        return 'end'
