from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import PoolMeta, Pool
from trytond.exceptions import UserError
from trytond.modules.purchase_trade.purchase import (TRIGGERS)
from trytond.transaction import Transaction
from decimal import getcontext, Decimal, ROUND_HALF_UP
from sql import Table
from trytond.pyson import Bool, Eval, Id, If

class PartyExecution(ModelSQL,ModelView):
    "Party Execution"
    __name__ = 'party.execution'

    party = fields.Many2One('party.party',"Party")
    area = fields.Many2One('country.region',"Area")
    percent = fields.Numeric("% targeted")
    achieved_percent = fields.Function(fields.Numeric("% achieved"),'get_percent')

    @staticmethod
    def _to_decimal(value):
        if value is None:
            return Decimal('0')
        if isinstance(value, Decimal):
            return value
        return Decimal(str(value))

    @classmethod
    def _round_percent(cls, value):
        return cls._to_decimal(value).quantize(
            Decimal('0.01'), rounding=ROUND_HALF_UP)

    def matches_country(self, country):
        if not self.area or not country or not getattr(country, 'region', None):
            return False
        region = country.region
        while region:
            if region.id == self.area.id:
                return True
            region = getattr(region, 'parent', None)
        return False

    def matches_shipment(self, shipment):
        location = getattr(shipment, 'to_location', None)
        country = getattr(location, 'country', None)
        return self.matches_country(country)

    @classmethod
    def compute_achieved_percent_for(cls, party, area):
        if not party or not area:
            return Decimal('0')
        Shipment = Pool().get('stock.shipment.in')
        shipments = Shipment.search([
                ('controller', '!=', None),
                ])
        execution = cls()
        execution.area = area
        shipments = [
            shipment for shipment in shipments
            if execution.matches_shipment(shipment)]
        total = len(shipments)
        if not total:
            return Decimal('0')
        achieved = sum(
            1 for shipment in shipments
            if shipment.controller and shipment.controller.id == party.id)
        return cls._round_percent(
            (Decimal(achieved) * Decimal('100')) / Decimal(total))

    def compute_achieved_percent(self):
        return self.__class__.compute_achieved_percent_for(
            self.party, self.area)

    def get_target_gap(self):
        return self._to_decimal(self.percent) - self.compute_achieved_percent()

    def get_percent(self,name):
        return self.compute_achieved_percent()

class PartyExecutionSla(ModelSQL,ModelView):
    "Party Execution Sla"
    __name__ = 'party.execution.sla'

    party = fields.Many2One('party.party',"Party")
    reference = fields.Char("Reference")
    product = fields.Many2One('product.product',"Product")
    date_from = fields.Date("From")
    date_to = fields.Date("To")
    places = fields.One2Many('party.execution.place','pes',"")

class PartyExecutionPlace(ModelSQL,ModelView):
    "Party Sla Place"
    __name__ = 'party.execution.place'

    pes = fields.Many2One('party.execution.sla',"Sla")
    country = fields.Many2One('country.country',"Country")
    location = fields.Many2One('stock.location',"Location")
    cost = fields.Numeric("Cost",digits=(16,4))
    mode = fields.Selection([
        ('lumpsum', 'Lump sum'),
        ('perqt', 'Per qt'),
        ('pprice', '% price'),
        ('rate', '% rate'),
        ('pcost', '% cost price'),
        ('ppack', 'Per packing'),
    ], 'Mode', required=True)
    currency = fields.Many2One('currency.currency',"Currency")
    unit = fields.Many2One('product.uom',"Unit",domain=[
            If(Eval('mode') == 'ppack',
                ('category', '=', 8),
                ()),
            ],
        states={
            'readonly': Eval('mode') != 'ppack',
            })
    
class Party(metaclass=PoolMeta):
    __name__ = 'party.party'

    tol_min = fields.Numeric("Tol - in %")
    tol_max = fields.Numeric("Tol + in %")
    wb = fields.Many2One('purchase.weight.basis',"Weight basis")
    association = fields.Many2One('purchase.association',"Association")
    origin =fields.Char("Origin")
    execution = fields.One2Many('party.execution','party',"")
    sla = fields.One2Many('party.execution.sla','party', "Sla")
    initial = fields.Char("Initials")

    def IsAvailableForControl(self,sh):
        return True

    def get_controller_execution_priority(self, shipment):
        best_rule = None
        best_gap = None
        for execution in self.execution or []:
            if not execution.matches_shipment(shipment):
                continue
            gap = execution.get_target_gap()
            if best_gap is None or gap > best_gap:
                best_gap = gap
                best_rule = execution
        return best_gap, best_rule
    
    def get_sla_cost(self, location):
        if self.sla:
            country = getattr(location, 'country', None)
            for sla in self.sla:
                SlaPlace = Pool().get('party.execution.place')
                domain = [
                    ('pes', '=', sla.id),
                    [
                        'OR',
                        ('location', '=', location),
                        ('location', '=', None),
                    ],
                ]
                if country:
                    domain.append([
                        'OR',
                        ('country', '=', country),
                        ('country', '=', None),
                    ])
                else:
                    domain.append(('country', '=', None))
                sp = SlaPlace.search(domain)
                sp = [
                    place for place in sp
                    if place.location or place.country
                ]
                sp.sort(
                    key=lambda place: (
                        0 if place.location and place.country else
                        1 if place.location else
                        2 if place.country else
                        3))
                if sp:
                    return sp[0].cost,sp[0].mode,sp[0].currency,sp[0].unit
        return None, None, None, None
    
    def get_alf(self):
        if self.name == 'CARGO CONTROL':
            return 105
        t = Table('alf')
        cursor = Transaction().connection.cursor()
        cursor.execute(*t.select(
            t.ALF_CODE,
            where=t.SHORT_NAME.ilike(f'%{self.name}%')
        ))
        rows = cursor.fetchall()
        if rows:
            return int(rows[0][0])

    @classmethod
    def _ensure_category(cls, party, category_name):
        if not (party and category_name):
            return
        Category = Pool().get('party.category')
        PartyCategory = Pool().get('party.party-party.category')
        cat = Category.search(['name', '=', category_name], limit=1)
        if not cat:
            return
        cat = cat[0]
        existing = PartyCategory.search([
            ('party', '=', party.id),
            ('category', '=', cat.id),
        ], limit=1)
        if existing:
            return
        pc = PartyCategory()
        pc.party = party.id
        pc.category = cat.id
        PartyCategory.save([pc])
        
    @classmethod
    def getPartyByName(cls, party, category=None):
        party = party.upper()
        p = cls.search([('name', '=', party)], limit=1)
        if p:
            cls._ensure_category(p[0], category)
            return p[0]
        else:
            p = cls()
            p.name = party
            cls.save([p])
            cls._ensure_category(p, category)
            return p
    
