# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from sql import Cast, Null
from sql.conditionals import Case
from sql.operators import Concat

from trytond.model import Check, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, If


class Location(metaclass=PoolMeta):
    __name__ = 'stock.location'
    production_location = fields.Many2One('stock.location', 'Production',
        states={
            'invisible': Eval('type') != 'warehouse',
            'required': Eval('type') == 'warehouse',
            },
        domain=[
            ('type', '=', 'production'),
            ])
    production_picking_location = fields.Many2One(
        'stock.location', "Production Picking",
        states={
            'invisible': Eval('type') != 'warehouse',
            },
        domain=[
            ('type', '=', 'storage'),
            ('parent', 'child_of', [Eval('id', -1)]),
            ],
        help="Where the production components are picked from.\n"
        "Leave empty to use the warehouse storage location.")
    production_output_location = fields.Many2One(
        'stock.location', "Production Output",
        states={
            'invisible': Eval('type') != 'warehouse',
            },
        domain=[
            ('type', '=', 'storage'),
            ('parent', 'child_of', [Eval('id', -1)]),
            ],
        help="Where the produced goods are stored.\n"
        "Leave empty to use the warehouse storage location.")


class Move(metaclass=PoolMeta):
    __name__ = 'stock.move'
    production_input = fields.Many2One(
        'production', "Production Input", readonly=True, ondelete='CASCADE',
        domain=[
            ('company', '=', Eval('company', -1)),
            If(Eval('production_output', None),
                ('id', '=', None),
                ()),
            ],
        states={
            'invisible': ~Eval('production_input'),
            })
    production_output = fields.Many2One(
        'production', "Production Output", readonly=True, ondelete='CASCADE',
        domain=[
            ('company', '=', Eval('company', -1)),
            If(Eval('production_input', None),
                ('id', '=', None),
                ()),
            ],
        states={
            'invisible': ~Eval('production_output'),
            })
    production = fields.Function(fields.Many2One(
            'production', "Production",
            states={
                'invisible': ~Eval('production'),
                },
            depends=['production_input', 'production_output']),
        'on_change_with_production', searcher='search_production')
    production_cost_price_updated = fields.Boolean(
        "Cost Price Updated", readonly=True,
        states={
            'invisible': ~Eval('production_input') & (Eval('state') == 'done'),
            })

    @classmethod
    def __setup__(cls):
        super().__setup__()
        t = cls.__table__()
        cls._sql_constraints += [
            ('production_single', Check(t, (
                        (t.production_input == Null)
                        | (t.production_output == Null))),
                'production.msg_stock_move_production_single'),
            ]
        cls._allow_modify_closed_period.add('production_cost_price_updated')

    @fields.depends(
        'production_input',
        '_parent_production_input.warehouse',
        '_parent_production_input.location',
        'from_location', 'to_location')
    def on_change_production_input(self):
        if self.production_input:
            if not self.from_location:
                self.from_location = self.production_input.picking_location
            if not self.to_location:
                self.to_location = self.production_input.location

    @fields.depends(
        'production_output',
        '_parent_production_output.company',
        '_parent_production_output.warehouse',
        '_parent_production_output.location',
        'from_location', 'to_location')
    def on_change_production_output(self):
        if self.production_output:
            if not self.from_location:
                self.from_location = self.production_output.location
            if not self.to_location:
                self.to_location = self.production_output.output_location
            if self.production_output.company:
                self.currency = self.production_output.company.currency
            if self.to_location and self.to_location.type == 'lost_found':
                self.currency = None

    @fields.depends(
        'production_output', '_parent_production_output.id', 'to_location')
    def on_change_to_location(self):
        try:
            super().on_change_to_location()
        except AttributeError:
            pass
        if (self.production_output
                and self.to_location
                and self.to_location.type == 'lost_found'):
            self.currency = None

    @fields.depends(
        'production_input', '_parent_production_input.id',
        'production_output', '_parent_production_output.id')
    def on_change_with_production(self, name=None):
        if self.production_input and self.production_input.id >= 0:
            return self.production_input
        elif self.production_output and self.production_output.id >= 0:
            return self.production_output

    @classmethod
    def search_production(cls, name, clause):
        _, operator, operand, *extra = clause
        if operator.startswith('!') or operator.startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        nested = clause[0][len(name):]
        return [bool_op,
            ('production_input' + nested, operator, operand, *extra),
            ('production_output' + nested, operator, operand, *extra),
            ]

    def set_effective_date(self):
        if not self.effective_date and self.production_input:
            self.effective_date = self.production_input.effective_start_date
        if not self.effective_date and self.production_output:
            self.effective_date = self.production_output.effective_date
        super().set_effective_date()

    @classmethod
    def on_modification(cls, mode, moves, field_names=None):
        super().on_modification(mode, moves, field_names=field_names)
        if mode == 'write' and 'cost_price' in field_names:
            cls.write(
                [m for m in moves if m.state == 'done' and m.production_input],
                {'production_cost_price_updated': True})

    def _rec_name_origin(self):
        return super()._rec_name_origin() or self.production


class ProductQuantitiesByWarehouseMove(metaclass=PoolMeta):
    __name__ = 'stock.product_quantities_warehouse.move'

    @classmethod
    def _get_document_models(cls):
        return super()._get_document_models() + ['production']

    def get_document(self, name):
        document = super().get_document(name)
        if self.move.production_input:
            document = str(self.move.production_input)
        if self.move.production_output:
            document = str(self.move.production_output)
        return document


class LotTrace(metaclass=PoolMeta):
    __name__ = 'stock.lot.trace'

    production_input = fields.Many2One('production', "Production Input")
    production_output = fields.Many2One('production', "Production Output")

    @classmethod
    def _columns(cls, tables):
        move = tables['move']
        return super()._columns(tables) + [
            move.production_input.as_('production_input'),
            move.production_output.as_('production_output'),
            ]

    @classmethod
    def get_documents(cls):
        pool = Pool()
        Model = pool.get('ir.model')
        return super().get_documents() + [
            ('production', Model.get_name('production'))]

    @classmethod
    def get_document(cls, tables):
        document = super().get_document(tables)
        sql_type = cls.document.sql_type().base
        move = tables['move']
        return Case(
            ((move.production_input != Null),
                Concat('production,',
                    Cast(move.production_input, sql_type))),
            ((move.production_output != Null),
                Concat('production,',
                    Cast(move.production_output, sql_type))),
            else_=document)

    def _get_upward_traces(self):
        if self.production_input:
            traces = set(self.production_input.outputs)
        else:
            traces = super()._get_upward_traces()
        return traces

    def _get_downward_traces(self):
        if self.production_output:
            traces = set(self.production_output.inputs)
        else:
            traces = super()._get_downward_traces()
        return traces
