from decimal import Decimal
from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
from trytond.exceptions import UserError
import datetime


class Lot(metaclass=PoolMeta):
    "Lot"
    __name__ = 'lot.lot'

    lot_role = fields.Selection([
        ('normal', 'Normal'),
        ('technical', 'Technical'),
    ], 'Role', required=True)

    split_operations = fields.One2Many(
        'lot.split.merge', 'source_lot', 'Split/Merge Ops'
    )

    @classmethod
    def default_lot_role(cls):
        return 'normal'

    # ---------------------------------------------------------------------
    # Technical helpers
    # ---------------------------------------------------------------------

    def _check_split_allowed(self):
        if self.lot_role != 'normal':
            raise UserError('Only normal lots can be split.')
        if self.lot_type != 'physic':
            raise UserError('Only physical lots can be split.')
        if self.IsDelivered():
            raise UserError('Delivered lots cannot be split.')

    def _create_technical_parent(self):
        Lot = Pool().get('lot.lot')
        parent = Lot(
            lot_name=f'TECH-{self.lot_name}',
            lot_role='technical',
            lot_type='virtual',
            lot_product=self.lot_product,
            lot_status=self.lot_status,
            lot_av='locked',
        )
        Lot.save([parent])
        return parent

    def _clone_hist_with_ratio(self, ratio):
        LotQtHist = Pool().get('lot.qt.hist')
        hist = []
        for h in self.lot_hist:
            hist.append(
                LotQtHist(
                    quantity_type=h.quantity_type,
                    quantity=h.quantity * ratio,
                    gross_quantity=h.gross_quantity * ratio,
                )
            )
        return hist

    # ---------------------------------------------------------------------
    # SPLIT
    # ---------------------------------------------------------------------

    def split_by_qt(self, splits):
        """
        splits = [
            {'lot_qt': 10, 'name': 'Lot A'},
            {'lot_qt': 20, 'name': 'Lot B'},
        ]
        """
        self._check_split_allowed()

        total_qt = sum([s['lot_qt'] for s in splits])
        if total_qt > self.lot_qt:
            raise UserError('Split quantity exceeds lot quantity.')

        Lot = Pool().get('lot.lot')
        Split = Pool().get('lot.split.merge')

        parent = self._create_technical_parent()
        children = []

        for s in splits:
            ratio = Decimal(s['lot_qt']) / Decimal(self.lot_qt)

            child = Lot(
                lot_name=s.get('name'),
                lot_parent=parent,
                lot_role='normal',
                lot_type='physic',
                lot_qt=s['lot_qt'],
                lot_unit=self.lot_unit,
                lot_product=self.lot_product,
                lot_status=self.lot_status,
                lot_av=self.lot_av,
                lot_unit_line=self.lot_unit_line,
                lot_hist=self._clone_hist_with_ratio(ratio),
            )
            children.append(child)

        Lot.save(children)

        # Update original lot
        self.lot_qt -= total_qt
        self.lot_hist = self._clone_hist_with_ratio(
            Decimal(self.lot_qt) / Decimal(self.lot_qt + total_qt)
        )
        Lot.save([self])

        ops = []
        for c in children:
            ops.append(
                Split(
                    operation='split',
                    source_lot=self,
                    target_lot=c,
                    lot_qt=c.lot_qt,
                    quantity=c.get_current_quantity(),
                    date=datetime.datetime.now(),
                )
            )
        Split.save(ops)

        return children

    def split_by_weight(self, splits):
        """
        splits = [
            {'quantity': Decimal('1200'), 'name': 'Lot A'},
            {'quantity': Decimal('800'), 'name': 'Lot B'},
        ]
        """
        self._check_split_allowed()

        total_weight = sum([s['quantity'] for s in splits])
        if total_weight > self.get_current_quantity():
            raise UserError('Split weight exceeds lot weight.')

        Lot = Pool().get('lot.lot')
        Split = Pool().get('lot.split.merge')

        parent = self._create_technical_parent()
        children = []

        for s in splits:
            ratio = s['quantity'] / self.get_current_quantity()

            child = Lot(
                lot_name=s.get('name'),
                lot_parent=parent,
                lot_role='normal',
                lot_type='physic',
                lot_qt=float(self.lot_qt * ratio) if self.lot_qt else None,
                lot_unit=self.lot_unit,
                lot_product=self.lot_product,
                lot_status=self.lot_status,
                lot_av=self.lot_av,
                lot_unit_line=self.lot_unit_line,
                lot_hist=self._clone_hist_with_ratio(ratio),
            )
            children.append(child)

        Lot.save(children)

        ops = []
        for c in children:
            ops.append(
                Split(
                    operation='split',
                    source_lot=self,
                    target_lot=c,
                    quantity=c.get_current_quantity(),
                    date=datetime.datetime.now(),
                )
            )
        Split.save(ops)

        self.lot_av = 'locked'
        Lot.save([self])

        return children

    # ---------------------------------------------------------------------
    # MERGE
    # ---------------------------------------------------------------------

    @classmethod
    def merge_lots(cls, lots):
        if len(lots) < 2:
            raise UserError('At least two lots are required for merge.')

        parent = lots[0].lot_parent
        if not parent or parent.lot_role != 'technical':
            raise UserError('Lots must share a technical parent.')

        product = lots[0].lot_product
        for l in lots:
            if l.lot_product != product:
                raise UserError('Cannot merge lots of different products.')

        Lot = Pool().get('lot.lot')
        Split = Pool().get('lot.split.merge')

        total_qt = sum([l.lot_qt or 0 for l in lots])
        total_weight = sum([l.get_current_quantity() for l in lots])

        merged = Lot(
            lot_name='MERGE-' + parent.lot_name,
            lot_role='normal',
            lot_type='physic',
            lot_product=product,
            lot_qt=total_qt,
            lot_unit=lots[0].lot_unit,
            lot_unit_line=lots[0].lot_unit_line,
        )

        merged.set_current_quantity(total_weight, total_weight, 1)
        Lot.save([merged])

        ops = []
        for l in lots:
            l.lot_av = 'locked'
            ops.append(
                Split(
                    operation='merge',
                    source_lot=l,
                    target_lot=merged,
                    lot_qt=l.lot_qt,
                    quantity=l.get_current_quantity(),
                    date=datetime.datetime.now(),
                )
            )

        Lot.save(lots)
        Split.save(ops)

        return merged

class LotSplitMerge(ModelSQL, ModelView):
    "Lot Split Merge"
    __name__ = 'lot.split.merge'

    operation = fields.Selection([
        ('split', 'Split'),
        ('merge', 'Merge'),
    ], 'Operation', required=True)

    source_lot = fields.Many2One(
        'lot.lot', 'Source Lot', required=True, ondelete='CASCADE'
    )
    target_lot = fields.Many2One(
        'lot.lot', 'Target Lot', required=True, ondelete='CASCADE'
    )

    lot_qt = fields.Float('Elements count')
    quantity = fields.Numeric('Weight', digits=(16, 5))

    date = fields.DateTime('Date', required=True)
    reversed_by = fields.Many2One(
        'lot.split.merge', 'Reversed By'
    )

class SplitWizardStart(ModelView):
    "Split Wizard Start"
    __name__ = 'lot.split.wizard.start'

    mode = fields.Selection([
        ('qt', 'By quantity'),
        ('weight', 'By weight'),
    ], 'Mode', required=True)

class SplitWizard(Wizard):
    "Lot Split Wizard"
    __name____ = 'lot.split.wizard'

    start = StateView(
        'lot.split.wizard.start',
        'purchase_trade.split_wizard_start_view',
        [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Split', 'split', 'tryton-ok', default=True),
        ]
    )

    split = StateTransition()

    def transition_split(self):
        Lot = Pool().get('lot.lot')
        lot = Lot(Transaction().context['active_id'])

        if self.start.mode == 'qt':
            lot.split_by_qt([
                {'lot_qt': lot.lot_qt / 2, 'name': lot.lot_name + '-1'},
                {'lot_qt': lot.lot_qt / 2, 'name': lot.lot_name + '-2'},
            ])
        else:
            q = lot.get_current_quantity()
            lot.split_by_weight([
                {'quantity': q / 2, 'name': lot.lot_name + '-1'},
                {'quantity': q / 2, 'name': lot.lot_name + '-2'},
            ])
        return 'end'