# forex.py
from trytond.model import ModelSingleton, ModelSQL, ModelView, fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Bool, Eval, Id, If
from trytond.transaction import Transaction, check_access
from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction
import jwt
import datetime
from decimal import Decimal
import logging

logger = logging.getLogger(__name__)

__all__ = ['Forex', 'ForexCoverPhysicalContract', 'ForexCoverFees']
__metaclass__ = PoolMeta


class Forex(ModelSQL, ModelView):
    'Forex Deal'
    __name__ = 'forex.forex'
    _rec_name = 'number'

    company = fields.Many2One('company.company', 'Company', required=True)

    number = fields.Char('Number', required=True)
    ex_no_ctr = fields.Char('Internal Ref')
    date = fields.Date('Date', required=True)
    value_date = fields.Date('Maturity Date')

    buy_currency = fields.Many2One('currency.currency', 'Buy Currency', required=True)
    buy_amount = fields.Numeric('Buy Amount', digits=(16, 2))

    for_currency = fields.Many2One('currency.currency', 'Sale Currency', required=True)
    for_amount = fields.Numeric('Sale Amount', digits=(16, 2))

    bank = fields.Many2One('bank', 'Party')
    reason = fields.Many2One('forex.category', 'Category', required=True)
    operation_type = fields.Selection([
        ('normal', 'Normal'),
        ('swap', 'Swap'),
    ], 'Operation Type', required=True)

    spot_rate = fields.Numeric('Rate', digits=(16, 6))
    differential = fields.Numeric('Differential', digits=(16, 6))
    rate = fields.Numeric('Agreed Rate', digits=(16, 6), required=True)

    confirmed = fields.Boolean('Confirmed')
    accepts_bls = fields.Boolean('Accepts B/Ls')
    voucher = fields.Boolean('Voucher')
    remarks = fields.Text("Remarks")

    cover_fees = fields.One2Many(
        'forex.cover.fees', 'forex', 'Cover Fees'
    )

    invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line',
        readonly=True)
    invoice_state = fields.Function(fields.Selection([
                ('', ''),
                ('invoiced', 'Invoiced'),
                ('paid', 'Paid'),
                ('cancelled', 'Cancelled'),
                ], "Invoice State",
            help="The current state of the invoice "
            "that the forex appears on."),
        'get_invoice_state')
    
    move = fields.Many2One('account.move', 'Move',
        readonly=True)
    move_state = fields.Function(fields.Selection([
                ('', ''),
                ('not executed', 'Not Executed'),
                ('executed', 'Executed'),
                ], "Move State",
            help="The current state of the move "
            "that the forex appears on."),
        'get_move_state')

    @classmethod
    def __setup__(cls):
        super(Forex, cls).__setup__()
        # cls.__access__.add('agent')
        cls._buttons.update({
                'invoice': {
                    'invisible': True, #Bool(Eval('invoice_line')),
                    #'depends': ['invoice_line'],
                    },
                'execute': {
                    'invisible': Bool(Eval('move')),
                    'depends': ['move'],
                    },
                })
    
    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @classmethod
    @ModelView.button
    def invoice(cls, forexs):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')
        invoices = []
        invoice_lines = []
        to_save = []
        for forex in forexs:
            invoice = cls._get_invoice(forex)
            invoices.append(invoice)
            invoice_line = cls._get_invoice_line(invoice, forex)
            invoice_lines.append(invoice_line)
            forex.invoice_line = invoice_line
            to_save.append(forex)
        Invoice.save(invoices)
        InvoiceLine.save(invoice_lines)
        # Invoice.update_taxes(invoices)
        cls.save(to_save)

    @classmethod
    @ModelView.button
    def execute(cls, forexs):
        Move = Pool().get('account.move')
        Period = Pool().get('account.period')
        for forex in forexs:
            move = Move()
            move_lines = forex._get_move_lines()
            move.journal = cls.get_journal()
            period = Period.find(forex.company, date=forex.value_date)
            move.date = forex.value_date
            move.period = period
            #move.origin = forex
            move.company = forex.company
            move.lines = move_lines
            Move.save([move])
            forex.create_exchange_move(move.lines[1].id)
            forex.move = move
            cls.save([forex])

    @classmethod
    def get_journal(cls):
        pool = Pool()
        Journal = pool.get('account.journal')

        journals = Journal.search([
                ('type', '=', 'cash'),
                ('name', '=', 'Forex'),
                ], limit=1)
        if journals:
            return journals[0]
        
    @classmethod
    def _get_invoice(cls, forex):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        payment_term = forex.bank.party.supplier_payment_term
        return Invoice(
            company=forex.company,
            type='in',
            journal=cls.get_journal(),
            party=forex.bank.party,
            invoice_address=forex.bank.party.address_get(type='invoice'),
            currency=forex.for_currency,
            account=forex.bank.party.account_payable_used,
            payment_term=payment_term,
            )

    @classmethod
    def _get_invoice_line(cls, invoice, forex):
        pool = Pool()
        InvoiceLine = pool.get('account.invoice.line')
        Product = pool.get('product.product')
        product = None
        invoice_line = InvoiceLine()
        ch_ct = Product.search(['name','=','Forex'])
        if ch_ct:
            product = ch_ct[0]
            invoice_line.account = product.account_expense_used
            invoice_line.unit = product.default_uom
        amount = invoice.currency.round(forex.for_amount)

        invoice_line.invoice = invoice
        invoice_line.currency = invoice.currency
        invoice_line.company = invoice.company
        invoice_line.type = 'line'
        # Use product.id to instantiate it with the correct context
        invoice_line.product = product.id
        invoice_line.quantity = 1

        # invoice_line.on_change_product()

        invoice_line.unit_price = amount
        return invoice_line

    def create_exchange_move(self,id):
        Currency = Pool().get('currency.currency')
        MoveLine = Pool().get('account.move.line')
        Configuration = Pool().get('account.configuration')
        configuration = Configuration(1)
        Period = Pool().get('account.period')
        Move = Pool().get('account.move')
        PaymentMethod = Pool().get('account.invoice.payment.method')
        pm = PaymentMethod.search(['name','=','Forex'])
        with Transaction().set_context(date=self.value_date):
            amount_converted = Currency.compute(self.buy_currency,self.buy_amount, self.company.currency)
            to_add = amount_converted - self.for_amount
            if to_add != 0:
                second_amount_to_add = Currency.compute(self.company.currency,to_add,self.buy_currency)
                line = MoveLine()
                line.account = pm[0].debit_account
                line.revaluate = id
                line.credit = -to_add if to_add < 0 else 0
                line.debit = to_add if to_add > 0 else 0
                # line.amount_second_currency = second_amount_to_add
                # line.second_currency = self.buy_currency 
                line.party = 33
                line.maturity_date = self.value_date
                logger.info("REVALUATE_ACC:%s",line)
                line_ = MoveLine()
                if to_add < 0:
                    line_.account = configuration.get_multivalue('currency_exchange_credit_account', company=self.company.id)
                else:
                    line_.account = configuration.get_multivalue('currency_exchange_debit_account', company=self.company.id)
                line_.credit = to_add if to_add > 0 else 0
                line_.debit = -to_add if to_add < 0 else 0
                # line_.amount_second_currency = -second_amount_to_add
                # line_.second_currency = self.buy_currency
                line_.maturity_date = self.value_date
                logger.info("REVALUATE_EX:%s",line_)
                move = Move()
                move.journal = configuration.get_multivalue('currency_exchange_journal', company=self.company.id)
                period = Period.find(self.company, date=self.value_date)
                move.date = self.value_date
                move.period = period
                #move.origin = forex
                move.company = self.company
                move.lines = [line,line_]
                Move.save([move])

    def _get_move_lines(self):
        '''
        Return move line
        '''
        MoveLine = Pool().get('account.move.line')
        Currency = Pool().get('currency.currency')
        PaymentMethod = Pool().get('account.invoice.payment.method')
        pm = PaymentMethod.search(['name','=','Forex'])
        move_lines = []
        if pm:
            line = MoveLine()
            line.amount_second_currency = self.buy_amount
            line.second_currency = self.buy_currency
            line.debit, line.credit = self.for_amount, 0
            line.account = pm[0].debit_account
            line.maturity_date = self.value_date
            line.description = 'Forex'
            line.party = self.bank.party
            line.origin = self
            move_lines.append(line)
            line = MoveLine()
            line.amount_second_currency = -self.buy_amount
            line.second_currency = self.buy_currency
            line.debit, line.credit = 0,self.for_amount
            line.account = pm[0].credit_account
            line.maturity_date = self.value_date
            line.description = 'Forex'
            line.party = self.bank.party
            line.origin = self
            move_lines.append(line)
        return move_lines
    
    def get_invoice_state(self, name):
        state = ''
        if self.invoice_line:
            state = 'invoiced'
            invoice = self.invoice_line.invoice
            if invoice and invoice.state in {'paid', 'cancelled'}:
                state = invoice.state
        return state

    def get_move_state(self, name):
        state = 'not executed'
        if self.move:
            state = 'executed'
        return state

    @classmethod
    def default_differential(cls):
        return Decimal(0)

    @fields.depends('buy_amount','for_amount','rate')
    def on_change_rate(self):
        if self.buy_amount and self.rate:
            self.for_amount = (Decimal(self.buy_amount) * Decimal(self.rate))
            self.spot_rate = None
            self.differential = None

    @fields.depends('buy_amount','for_amount','rate')
    def on_change_for_amount(self):
        if self.buy_amount and self.for_amount:
            self.rate = round(Decimal(self.for_amount) / Decimal(self.buy_amount),6)
            self.spot_rate = None
            self.differential = None

    @fields.depends('buy_amount','for_amount','spot_rate','differential','rate')
    def on_change_spot_rate(self):
        if self.buy_amount and self.rate and self.for_amount:
            self.differential = Decimal(self.rate) - Decimal(self.spot_rate if self.spot_rate else 0)

    @fields.depends('buy_amount','for_amount','spot_rate','differential','rate')
    def on_change_differential(self):
        if self.buy_amount and self.differential and self.for_amount:
            self.spot_rate = Decimal(self.rate if self.rate else 0) - Decimal(self.differential)

class PForex(metaclass=PoolMeta):
    'Forex Deal'
    __name__ = 'forex.forex'

    cover_physical_contracts = fields.One2Many(
        'forex.cover.physical.contract', 'forex', 'Cover Physical Purchase'
    )

class SForex(metaclass=PoolMeta):
    'Forex Deal'
    __name__ = 'forex.forex'

    cover_physical_sales = fields.One2Many(
        'forex.cover.physical.sale', 'forex', 'Cover Physical Sale'
    )

class ForexCategory(ModelSQL, ModelView):
    'Forex Category'
    __name__ = 'forex.category'

    name = fields.Char('Name')

class ForexCoverPhysicalContract(ModelSQL, ModelView):
    'Forex Cover Physical Contract'
    __name__ = 'forex.cover.physical.contract'

    forex = fields.Many2One('forex.forex', 'Forex', required=True, ondelete='CASCADE')
    contract = fields.Many2One('purchase.purchase', 'Purchase', required=True)
    amount = fields.Numeric('Amount', digits=(16, 2))
    quantity = fields.Numeric('Quantity', digits=(16, 5))
    unit = fields.Many2One('product.uom',"Unit")

class ForexCoverPhysicalSale(ModelSQL, ModelView):
    'Forex Cover Physical Contract'
    __name__ = 'forex.cover.physical.sale'

    forex = fields.Many2One('forex.forex', 'Forex', required=True, ondelete='CASCADE')
    contract = fields.Many2One('sale.sale', 'Sale', required=True)
    amount = fields.Numeric('Amount', digits=(16, 2))
    quantity = fields.Numeric('Quantity', digits=(16, 5))
    unit = fields.Many2One('product.uom',"Unit")

class ForexCoverFees(ModelSQL, ModelView):
    'Forex Cover Fees'
    __name__ = 'forex.cover.fees'

    forex = fields.Many2One('forex.forex', 'Forex', required=True, ondelete='CASCADE')
    description = fields.Char('Description')
    amount = fields.Numeric('Amount', digits=(16, 2))
    currency = fields.Many2One('currency.currency', 'Currency')

class ForexReport(Wizard):
    'Forex report'
    __name__ = 'forex.report'

    start = StateAction('purchase_trade.act_forex_bi')

    def do_start(self, action):
        pool = Pool()
        # action['views'].reverse()
        return action, {'res_id': [1]}

class ForexBI(ModelSingleton,ModelSQL, ModelView):
    'Forex BI'
    __name__ = 'forex.bi'

    input = fields.Text("BI")
    metabase = fields.Function(fields.Text(""),'get_bi')

    def get_bi(self,name=None):
            Configuration = Pool().get('gr.configuration')
            config = Configuration.search(['id','>',0])[0]
        
            payload = {
                "resource": {"dashboard": config.forex_id},
                "params": {},
                "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
            }
            token = jwt.encode(payload, config.payload, algorithm="HS256")
            logger.info("TOKEN:%s",token)
            if config.dark:
                url = f"metabase:{config.bi}/embed/dashboard/{token}#theme=night&bordered=true&titled=true"
            else:
                url = f"metabase:{config.bi}/embed/dashboard/{token}#bordered=true&titled=true"
            return url
    