# This file is part of lims_quality_control module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from datetime import datetime
from dateutil.relativedelta import relativedelta

from trytond.model import fields, ModelView
from trytond.wizard import Wizard, StateView, StateTransition, StateAction, \
    StateReport, Button
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
from trytond.pyson import PYSONEncoder, Bool, Not, Eval
from trytond.report import Report
from trytond.exceptions import UserError
from trytond.i18n import gettext


class LabWorkYear(metaclass=PoolMeta):
    __name__ = 'lims.lab.workyear'

    default_entry_quality = fields.Many2One('lims.entry',
        'Default entry quality')


class Fraction(metaclass=PoolMeta):
    __name__ = 'lims.fraction'

    def _get_stock_move(self):
        move = super()._get_stock_move()
        if self.sample.quality:
            move.product = self.sample.lot.product.id
            move.lot = self.sample.lot.id
        return move


class Sample(metaclass=PoolMeta):
    __name__ = 'lims.sample'

    quality = fields.Boolean('Quality')
    lot = fields.Many2One('stock.lot', 'Lot', readonly=True)
    test_state = fields.Selection([
        ('pending', 'Pending'),
        ('done', 'Done'),
        ('countersample', 'Countersample'),
        ], 'Test State', readonly=True)
    product = fields.Function(fields.Many2One('product.product',
        'Product'), 'on_change_with_product', searcher='search_product')
    quality_test = fields.Many2One('lims.quality.test', 'Test', readonly=True)
    countersample_original_sample = fields.Many2One('lims.sample',
        'Countersample Original sample', readonly=True,
        states={'invisible': Not(Bool(Eval('countersample_original_sample')))})
    countersamples = fields.One2Many('lims.sample',
        'countersample_original_sample', 'Countersamples', readonly=True)
    countersample = fields.Function(fields.Many2One('lims.sample',
        'Countersample'), 'get_countersample')

    @staticmethod
    def default_test_state():
        return 'pending'

    @fields.depends('lot')
    def on_change_with_product(self, name=None):
        if self.lot:
            return self.lot.product.id

    @classmethod
    def search_product(cls, name, clause):
        return [('lot.' + name,) + tuple(clause[1:])]

    @fields.depends('countersamples')
    def get_countersample(self, name):
        if self.countersamples:
            return self.countersamples[0].id


class TakeSampleStart(ModelView):
    'Take Sample Start'
    __name__ = 'lims.take.sample.start'

    date = fields.Date('Date', required=True)
    label = fields.Char('Label', required=True)
    attributes = fields.Dict('lims.sample.attribute', 'Attributes')
    sample = fields.Many2One('lims.sample', 'Sample')

    @staticmethod
    def default_date():
        Date = Pool().get('ir.date')
        return Date.today()


class TakeSampleResult(ModelView):
    'Take Sample Result'
    __name__ = 'lims.take.sample.result'

    message = fields.Text('Message', readonly=True)


class TakeSample(Wizard):
    'Take Sample'
    __name__ = 'lims.take.sample'

    start = StateView('lims.take.sample.start',
        'lims_quality_control.lims_take_sample_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Confirm', 'confirm', 'tryton-ok', default=True),
            ])
    confirm = StateTransition()
    result = StateView('lims.take.sample.result',
        'lims_quality_control.lims_take_sample_result_view_form', [
            Button('Close', 'end', 'tryton-cancel'),
            Button('Print', 'print_', 'tryton-print', default=True),
            ])
    print_ = StateReport('lims.sample.labels.report')

    def transition_confirm(self):
        self.start.sample = self.create_sample()
        return 'result'

    def create_sample(self):
        pool = Pool()
        Config = pool.get('lims.configuration')
        QualityConfig = pool.get('lims.quality.configuration')
        Lot = pool.get('stock.lot')
        LabWorkYear = pool.get('lims.lab.workyear')
        Entry = pool.get('lims.entry')
        Sample = pool.get('lims.sample')
        Fraction = pool.get('lims.fraction')

        lot = Lot(Transaction().context.get('active_id'))
        if not lot.product.template.product_type:
            raise UserError(gettext('lims.msg_no_product_product_type'))
        if not lot.product.template.matrix:
            raise UserError(gettext('lims.msg_no_product_matrix'))

        config = Config(1)
        if not config.qc_fraction_type:
            raise UserError(gettext(
                'lims_quality_control.msg_no_qc_fraction_type'))
        fraction_type = config.qc_fraction_type
        if (not fraction_type.default_package_type or
                not fraction_type.default_fraction_state):
            raise UserError(gettext(
                'lims_quality_control.msg_no_qc_default_configuration'))

        quality_config = QualityConfig(1)

        workyear_id = LabWorkYear.find()
        workyear = LabWorkYear(workyear_id)
        if not workyear.default_entry_quality:
            raise UserError(gettext(
                'lims_quality_control.msg_no_entry_quality'))

        entry = Entry(workyear.default_entry_quality.id)
        if not entry.party.entry_zone and config.zone_required:
            raise UserError(gettext('lims.msg_no_party_zone',
                party=entry.party.rec_name))
        zone_id = entry.party.entry_zone and entry.party.entry_zone.id or None

        obj_description = self._get_obj_description(lot.product)

        # new sample
        new_sample, = Sample.create([{
            'quality': True,
            'lot': lot.id,
            'attributes': self.start.attributes,
            'entry': entry.id,
            'party': entry.party.id,
            'date': datetime.now(),
            'product_type': lot.product.template.product_type.id,
            'matrix': lot.product.template.matrix.id,
            'zone': zone_id,
            'label': self.start.label,
            'obj_description': obj_description,
            'packages': [('create', [{
                'quantity': 1,
                'type': fraction_type.default_package_type.id,
                'state': fraction_type.default_fraction_state.id,
                }])],
            'fractions': [],
            }])

        # new fraction
        fraction_default = {
            'sample': new_sample.id,
            'type': fraction_type.id,
            'storage_location': quality_config.sample_location.id,
            'services': [],
            }
        if fraction_type.max_storage_time:
            fraction_default['storage_time'] = fraction_type.max_storage_time
        elif quality_config.sample_location.storage_time:
            fraction_default['storage_time'] = (
                quality_config.sample_location.storage_time)
        else:
            fraction_default['storage_time'] = 3
        new_fraction, = Fraction.create([fraction_default])

        return new_sample

    def _get_obj_description(self, product):
        cursor = Transaction().connection.cursor()
        ObjectiveDescription = Pool().get('lims.objective_description')

        cursor.execute('SELECT id '
            'FROM "' + ObjectiveDescription._table + '" '
            'WHERE product_type = %s '
                'AND matrix = %s',
            (product.template.product_type.id, product.template.matrix.id))
        res = cursor.fetchone()
        return res and res[0] or None

    def default_result(self, fields):
        default = {'message': '%s: %s' % (
            gettext('lims_quality_control.msg_sample_created'),
            self.start.sample.number)}
        return default

    def do_print_(self, action):
        data = {
            'sample': self.start.sample.id,
            }
        return action, data

    def transition_print_(self):
        return 'end'


class CountersampleCreateStart(ModelView):
    'Countersample Create Start'
    __name__ = 'lims.countersample.create.start'

    location = fields.Many2One('stock.location', 'Location', required=True)
    quantity = fields.Float('Quantity', required=True)
    unit = fields.Many2One('product.uom', 'UoM', required=True,
        domain=[
            ('category', '=', Eval('product_uom_category')),
            ('factor', '<=', Eval('product_factor')),
            ])
    product_uom_category = fields.Many2One('product.uom.category',
        'Product Uom Category')
    product_factor = fields.Float("Factor", digits=(12, 12))
    comments = fields.Text('Comments')
    countersamples = fields.Many2Many(
        'lims.sample', None, None, 'Countersamples')


class CountersampleCreate(Wizard):
    'Countersample Create'
    __name__ = 'lims.countersample.create'

    start = StateTransition()
    ask = StateView('lims.countersample.create.start',
        'lims_quality_control.lims_countersample_create_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Create', 'create_', 'tryton-ok', default=True),
            ])
    create_ = StateTransition()
    open_ = StateAction('lims_quality_control.act_lims_sample_list')

    def default_ask(self, fields):
        Sample = Pool().get('lims.sample')
        res = {}
        sample = Sample(Transaction().context['active_id'])
        res['product_uom_category'] = sample.product.default_uom_category.id
        res['product_factor'] = sample.product.default_uom.factor
        return res

    def transition_start(self):
        Sample = Pool().get('lims.sample')

        if not Transaction().context['active_ids']:
            raise UserError(gettext(
                    'lims_quality_control.msg_records_not_selected'))

        samples = Sample.browse(Transaction().context['active_ids'])
        for sample in samples:
            if sample.test_state != 'done':
                raise UserError(gettext(
                        'lims_quality_control.msg_not_test_sample'))
            if sample.countersample:
                raise UserError(gettext(
                        'lims_quality_control.msg_has_countersample'))
        return 'ask'

    def transition_create_(self):
        countersamples = self.create_countersample()
        self.ask.countersamples = [sample.id for sample in countersamples]
        return 'open_'

    def create_countersample(self):
        pool = Pool()
        Sample = pool.get('lims.sample')
        Fraction = pool.get('lims.fraction')
        Move = pool.get('stock.move')
        Date = pool.get('ir.date')

        samples = Sample.browse(Transaction().context.get('active_ids'))

        countersamples = []
        for sample in samples:

            # new countersample
            new_countersample, = Sample.create([{
                'quality': True,
                'lot': sample.lot.id,
                'attributes': sample.attributes,
                'entry': sample.entry.id,
                'party': sample.party.id,
                'date': datetime.now(),
                'product_type': sample.product_type.id,
                'matrix': sample.matrix.id,
                'zone': sample.zone and sample.zone.id or None,
                'label': sample.label,
                'obj_description': sample.obj_description,
                'packages': [('create', [{
                    'quantity': p.quantity,
                    'type': p.type.id,
                    'state': p.state.id,
                    } for p in sample.packages])],
                'countersample_original_sample': sample.id,
                'test_state': 'countersample',
                'comments': self.ask.comments,
                'fractions': [],
                }])

            # new fraction
            fraction_default = {
                'sample': new_countersample.id,
                'type': sample.fractions[0].type.id,
                'storage_location': self.ask.location.id,
                'storage_time': sample.fractions[0].storage_time,
                'countersample_location': self.ask.location.id,
                'countersample_date': Date.today(),
                'expiry_date': Date.today() + relativedelta(
                    months=sample.fractions[0].storage_time),
                'services': [],
                }
            new_fraction, = Fraction.create([fraction_default])

            moves = self._get_stock_moves([new_fraction],
                self.ask.quantity, self.ask.unit)
            Move.do(moves)
            countersamples.append(new_countersample)

        return countersamples

    def do_open_(self, action):
        action['pyson_domain'] = PYSONEncoder().encode([
            ('id', 'in', [sample.id for sample in self.ask.countersamples]),
            ])
        action['views'].reverse()
        return action, {
            'res_id': [self.ask.countersamples[0].id],
            }

    def _get_stock_moves(self, fractions, quantity, unit):
        pool = Pool()
        User = pool.get('res.user')
        Move = pool.get('stock.move')

        company = User(Transaction().user).company

        moves = []
        for fraction in fractions:
            with Transaction().set_user(0, set_context=True):
                move = Move()
            move.product = fraction.sample.lot.product.id
            move.lot = fraction.sample.lot.id
            move.fraction = fraction.id
            move.quantity = quantity or 1
            move.uom = unit
            move.from_location = \
                fraction.sample.countersample_original_sample.fractions[
                    0].storage_location.id
            move.to_location = fraction.countersample_location.id
            move.company = company
            move.planned_date = fraction.countersample_date
            move.origin = fraction
            if move.on_change_with_unit_price_required():
                move.unit_price = 0
                move.currency = company.currency
            move.state = 'draft'
            move.save()
            moves.append(move)
        return moves


class SampleLabels(Report):
    'Sample Labels'
    __name__ = 'lims.sample.labels.report'

    @classmethod
    def get_context(cls, records, header, data):
        Sample = Pool().get('lims.sample')

        report_context = super().get_context(records, header, data)
        labels = []
        if data.get('sample'):
            records = [Sample(data.get('sample'))]
        for sample in records:
            labels.append(sample)
        report_context['labels'] = labels

        return report_context
