# -*- coding: utf-8 -*-
from decimal import Decimal
import logging

from trytond.pool import Pool
from trytond.transaction import Transaction

logger = logging.getLogger(__name__)


class ContractFactory:
    """
    Factory métier pour créer des Purchase depuis Sale
    ou des Sale depuis Purchase.

    Compatible :
    - Wizard (n contrats)
    - Appel direct depuis un modèle (1 contrat)
    """

    @classmethod
    def create_contracts(cls, contracts, *, type_, ct):
        """
        :param contracts: iterable de contracts (wizard lines)
        :param type_: 'Purchase' ou 'Sale'
        :param ct: objet contenant le contexte (lot, product, unit, matched...)
        :return: liste des contracts créés
        """
        pool = Pool()

        Sale = pool.get('sale.sale')
        Purchase = pool.get('purchase.purchase')
        SaleLine = pool.get('sale.line')
        PurchaseLine = pool.get('purchase.line')
        Date = pool.get('ir.date')

        created = []

        base_contract = (
            ct.lot.sale_line.sale
            if type_ == 'Purchase'
            else ct.lot.line.purchase
        )

        for c in contracts:
            contract = Purchase() if type_ == 'Purchase' else Sale()
            line = PurchaseLine() if type_ == 'Purchase' else SaleLine()

            # ---------- CONTRACT ----------
            parts = c.currency_unit.split("_")
            contract.currency = int(parts[0]) or 1
            contract.party = c.party
            contract.crop = c.crop
            contract.tol_min = c.tol_min
            contract.tol_max = c.tol_max
            contract.reference = c.reference
            contract.from_location = c.from_location
            contract.to_location = c.to_location
            contract.company = 6
            if type_ == 'Purchase':
                contract.purchase_date = Date.today()
            else:
                contract.sale_date = Date.today()

            cls._apply_locations(contract, base_contract, type_)
            cls._apply_party_data(contract, c.party, type_)
            cls._apply_payment_term(contract, c.party, type_)

            contract.incoterm = c.incoterm

            if c.party.addresses:
                contract.invoice_address = c.party.addresses[0]
                if type_ == 'Sale':
                    contract.shipment_address = c.party.addresses[0]

            contract.save()

            # ---------- LINE ----------
            line.quantity = c.quantity
            line.quantity_theorical = c.quantity
            line.product = ct.product
            line.unit = ct.unit
            line.price_type = c.price_type
            line.created_by_code = ct.matched
            line.premium = Decimal(0)

            if type_ == 'Purchase':
                line.purchase = contract.id
            else:
                line.sale = contract.id

            cls._apply_price(line, c, parts)

            line.del_period = c.del_period
            line.from_del = c.from_del
            line.to_del = c.to_del

            line.save()

            logger.info("CREATE_ID:%s", contract.id)
            logger.info("CREATE_LINE_ID:%s", line.id)

            if ct.matched:
                cls._create_lot(line, c, ct, type_)

            created.append(contract)

        return created

    # -------------------------------------------------------------------------
    # Helpers
    # -------------------------------------------------------------------------

    @staticmethod
    def _apply_locations(contract, base, type_):
        if not (base.from_location and base.to_location):
            return

        if type_ == 'Purchase':
            contract.to_location = base.from_location
        else:
            contract.from_location = base.to_location

        if (base.from_location.type == 'supplier'
                and base.to_location.type == 'customer'):
            contract.from_location = base.from_location
            contract.to_location = base.to_location

    @staticmethod
    def _apply_party_data(contract, party, type_):
        if party.wb:
            contract.wb = party.wb
        if party.association:
            contract.association = party.association

    @staticmethod
    def _apply_payment_term(contract, party, type_):
        if type_ == 'Purchase' and party.supplier_payment_term:
            contract.payment_term = party.supplier_payment_term
        elif type_ == 'Sale' and party.customer_payment_term:
            contract.payment_term = party.customer_payment_term

    @staticmethod
    def _apply_price(line, c, parts):
        if int(parts[0]) == 0:
            line.enable_linked_currency = True
            line.linked_currency = 1
            line.linked_unit = int(parts[1])
            line.linked_price = c.price
            line.unit_price = line.get_price_linked_currency()
        else:
            line.unit_price = c.price if c.price else Decimal(0)

    # -------------------------------------------------------------------------
    # LOT / MATCHING (repris tel quel du wizard)
    # -------------------------------------------------------------------------

    @classmethod
    def _create_lot(cls, line, c, ct, type_):
        pool = Pool()
        Lot = pool.get('lot.lot')
        LotQtHist = pool.get('lot.qt.hist')
        LotQtType = pool.get('lot.qt.type')

        lot = Lot()

        if type_ == 'Purchase':
            lot.line = line.id
        else:
            lot.sale_line = line.id

        lot.lot_qt = None
        lot.lot_unit = None
        lot.lot_unit_line = line.unit
        lot.lot_quantity = round(line.quantity, 5)
        lot.lot_gross_quantity = None
        lot.lot_status = 'forecast'
        lot.lot_type = 'virtual'
        lot.lot_product = line.product

        lqtt = LotQtType.search([('sequence', '=', 1)])
        if lqtt:
            lqh = LotQtHist()
            lqh.quantity_type = lqtt[0]
            lqh.quantity = round(lot.lot_quantity, 5)
            lqh.gross_quantity = round(lot.lot_quantity, 5)
            lot.lot_hist = [lqh]

        lot.save()

        vlot = ct.lot
        shipment_origin = cls._get_shipment_origin(ct)

        qt = c.quantity

        if type_ == 'Purchase':
            if not lot.updateVirtualPart(qt, shipment_origin, vlot):
                lot.createVirtualPart(qt, shipment_origin, vlot)

            # Decrease forecasted virtual part non matched
            lot.updateVirtualPart(-qt, shipment_origin, vlot, 'only sale')
        else:
            if not vlot.updateVirtualPart(qt, shipment_origin, lot):
                vlot.createVirtualPart(qt, shipment_origin, lot)

            # Decrease forecasted virtual part non matched
            vlot.updateVirtualPart(-qt, shipment_origin, None)

    @staticmethod
    def _get_shipment_origin(ct):
        if ct.shipment_in:
            return 'stock.shipment.in,%s' % ct.shipment_in.id
        if ct.shipment_internal:
            return 'stock.shipment.internal,%s' % ct.shipment_internal.id
        if ct.shipment_out:
            return 'stock.shipment.out,%s' % ct.shipment_out.id
        return None
