# 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 collections import defaultdict

from trytond.model import DeactivableMixin, ModelSQL, ModelView, Unique, fields
from trytond.modules.currency.fields import Monetary
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction


class Fee(DeactivableMixin, ModelSQL, ModelView):
    __name__ = 'account.dunning.fee'
    name = fields.Char('Name', required=True, translate=True)
    product = fields.Many2One('product.product', 'Product', required=True,
        domain=[
            ('type', '=', 'service'),
            ('template.type', '=', 'service'),
            ])
    journal = fields.Many2One('account.journal', 'Journal', required=True)
    compute_method = fields.Selection([
            ('list_price', 'List Price'),
            ('percentage', 'Percentage'),
            ], 'Compute Method', required=True,
        help='Method to compute the fee amount')
    percentage = fields.Numeric(
        "Percentage", digits=(None, 8),
        states={
            'invisible': Eval('compute_method') != 'percentage',
            'required': Eval('compute_method') == 'percentage',
            })

    def get_list_price(self, dunning):
        pool = Pool()
        Product = pool.get('product.product')
        with Transaction().set_context(company=dunning.company.id):
            product = Product(self.product)
            return product.list_price_used

    def get_amount(self, dunning):
        'Return fee amount and currency'
        amount, currency = None, None
        if self.compute_method == 'list_price':
            currency = dunning.company.currency
            amount = currency.round(self.get_list_price(dunning))
        elif self.compute_method == 'percentage':
            if dunning.second_currency:
                amount = dunning.amount_second_currency
                currency = dunning.second_currency
            else:
                amount = dunning.amount
                currency = dunning.company.currency
            amount = currency.round(amount * self.percentage)
        return amount, currency


class Level(metaclass=PoolMeta):
    __name__ = 'account.dunning.level'
    fee = fields.Many2One('account.dunning.fee', 'Fee')


class Dunning(metaclass=PoolMeta):
    __name__ = 'account.dunning'

    fees = fields.One2Many(
        'account.dunning.fee.dunning_level', 'dunning', 'Fees', readonly=True)

    @classmethod
    def process(cls, dunnings):
        pool = Pool()
        FeeDunningLevel = pool.get('account.dunning.fee.dunning_level')

        fees = []
        for dunning in dunnings:
            if dunning.blocked or not dunning.level.fee:
                continue
            if dunning.level in {f.level for f in dunning.fees}:
                continue
            fee = FeeDunningLevel(dunning=dunning, level=dunning.level)
            fee.amount, fee.currency = dunning.level.fee.get_amount(dunning)
            fees.append(fee)
        FeeDunningLevel.save(fees)
        FeeDunningLevel.process(fees)

        super().process(dunnings)


class FeeDunningLevel(ModelSQL, ModelView):
    __name__ = 'account.dunning.fee.dunning_level'

    dunning = fields.Many2One(
        'account.dunning', "Dunning", required=True)
    level = fields.Many2One('account.dunning.level', 'Level', required=True)
    amount = Monetary(
        "Amount", currency='currency', digits='currency')
    currency = fields.Many2One('currency.currency', 'Currency')
    moves = fields.One2Many('account.move', 'origin', 'Moves', readonly=True)

    @classmethod
    def __setup__(cls):
        super().__setup__()
        t = cls.__table__()
        cls._sql_constraints = [
            ('dunning_level_unique', Unique(t, t.dunning, t.level),
                'account_dunning_fee.msg_fee_dunning_level_unique'),
            ]

    def get_rec_name(self, name):
        return '%s @ %s' % (self.dunning.rec_name, self.level.rec_name)

    @classmethod
    def search_rec_name(cls, name, clause):
        _, operator, value = clause
        if operator.startswith('!') or operator.startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
            ('dunning.rec_name', *clause[1:]),
            ('level.rec_name', *clause[1:]),
            ]

    @classmethod
    def process(cls, fees):
        pool = Pool()
        Move = pool.get('account.move')
        moves = []
        for fee in fees:
            move = fee.get_move_process()
            moves.append(move)
        Move.save(moves)
        Move.post(moves)

    def get_move_process(self):
        pool = Pool()
        Move = pool.get('account.move')
        Line = pool.get('account.move.line')
        Date = pool.get('ir.date')
        Period = pool.get('account.period')
        Currency = pool.get('currency.currency')

        with Transaction().set_context(company=self.dunning.company.id):
            today = Date.today()
        move = Move()
        move.company = self.dunning.company
        move.journal = self.level.fee.journal
        move.date = today
        move.period = Period.find(move.company, date=today)
        move.origin = self
        move.description = self.level.fee.name

        line = Line()
        if self.currency == move.company.currency:
            line.debit = self.amount
        else:
            line.second_currency = self.currency
            line.amount_second_currency = self.amount
            line.debit = Currency.compute(
                self.currency, self.amount, move.company.currency)
        line.account = self.dunning.line.account
        line.party = self.dunning.line.party

        counterpart = Line()
        counterpart.credit = line.debit
        counterpart.account = self.level.fee.product.account_revenue_used
        if counterpart.account and counterpart.account.party_required:
            counterpart.party = self.dunning.party

        move.lines = [line, counterpart]
        return move

    # TODO create move with taxes on reconcile of process move line


class Letter(metaclass=PoolMeta):
    __name__ = 'account.dunning.letter'

    @classmethod
    def get_party_letter(cls):
        PartyLetter = super().get_party_letter()

        class PartyLetterFee(PartyLetter):

            @property
            def fees(self):
                fees = defaultdict(int)
                fees.update(super().fees)
                for dunning in self.dunnings:
                    for fee in dunning.fees:
                        fees[fee.currency] += fee.amount
                return fees

        return PartyLetterFee
