# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
from decimal import Decimal
from io import BytesIO

from lxml import etree

from trytond.i18n import gettext
from trytond.modules.account_statement.exceptions import ImportStatementError
from trytond.pool import Pool, PoolMeta


class StatementImportStart(metaclass=PoolMeta):
    __name__ = 'account.statement.import.start'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.file_format.selection.extend([
                ('camt_052_001', "CAMT.052.001"),
                ('camt_053_001', "CAMT.053.001"),
                ('camt_054_001', "CAMT.054.001"),
                ])


class StatementImport(metaclass=PoolMeta):
    __name__ = 'account.statement.import'

    def parse_camt(self):
        file_ = BytesIO(self.start.file_)
        tree = etree.parse(file_)
        root = tree.getroot()
        namespaces = dict(root.nsmap)
        namespaces['ns'] = namespaces.pop(None)
        for camt_statement in root.xpath(
                './ns:BkToCstmrStmt/ns:Stmt | '
                './ns:BkToCstmrAcctRpt/ns:Rpt | '
                './ns:BkToCstmrDbtCdtNtfctn/ns:Ntfctn', namespaces=namespaces):
            statement = self.camt_statement(camt_statement)
            origins = []
            for entry in camt_statement.iterfind('./{*}Ntry'):
                origins.extend(self.camt_origin(camt_statement, entry))
            if origins:
                statement.number_of_lines = len(origins)
                statement.origins = origins
                yield statement

    parse_camt_052_001 = parse_camt
    parse_camt_053_001 = parse_camt
    parse_camt_054_001 = parse_camt

    def camt_statement(self, camt_statement):
        pool = Pool()
        Statement = pool.get('account.statement')
        Journal = pool.get('account.statement.journal')

        statement = Statement()
        statement.name = camt_statement.findtext('./{*}Id')
        statement.company = self.start.company
        account_number = self.camt_account_number(camt_statement)
        account_currency = self.camt_account_currency(camt_statement)
        statement.journal = Journal.get_by_bank_account(
            statement.company, account_number, currency=account_currency)
        if not statement.journal:
            raise ImportStatementError(
                gettext('account_statement.msg_import_no_journal',
                    account=account_number))
        statement.date = self.camt_statement_date(camt_statement)
        statement.start_balance, statement.end_balance = (
            self.camt_statement_balances(camt_statement))
        statement.total_amount = self.camt_statement_total(camt_statement)
        return statement

    def _camt_amount(self, entry, detail=None):
        if detail is not None:
            amount = Decimal(detail.findtext('./{*}AmtDtls/{*}TxAmt/{*}Amt'))
        else:
            amount = Decimal(entry.findtext('./{*}Amt'))
        if entry.findtext('./{*}CdtDbtInd') == 'DBIT':
            amount *= -1
        return amount

    def camt_account_number(self, camt_statement):
        number = camt_statement.findtext('./{*}Acct/{*}Id/{*}IBAN')
        if not number:
            number = camt_statement.findtext('./{*}Acct/{*}Id/{*}Othr/{*}Id')
        return number

    def camt_account_currency(self, camt_statement):
        return camt_statement.findtext('./{*}Acct/{*}Ccy')

    def camt_statement_date(self, camt_statement):
        pool = Pool()
        Date = pool.get('ir.date')
        datetime = camt_statement.findtext('./{*}CreDtTm')
        if datetime:
            return dt.datetime.fromisoformat(datetime).date()
        else:
            return Date.today()

    def camt_statement_balances(self, camt_statement):
        namespaces = dict(camt_statement.nsmap)
        namespaces['ns'] = namespaces.pop(None)
        start_balance = end_balance = None
        for code in [
                'OPBD',  # Opening Balance
                'PRCD',  # Previous Closing Balance
                'CLBD',  # Closing Balance
                'ITBD',  # Interim Balance
                ]:
            balances = camt_statement.xpath(
                f'./ns:Bal/ns:Tp/ns:CdOrPrtry/ns:Cd[text()="{code}"]'
                '/../../..', namespaces=namespaces)
            if balances:
                if code in {'OPBD', 'PRCD'}:
                    start_balance = self._camt_amount(balances[0])
                elif code == 'CLBD':
                    end_balance = self._camt_amount(balances[-1])
                else:
                    start_balance = self._camt_amount(balances[0])
                    end_balance = self._camt_amount(balances[-1])
        return start_balance, end_balance

    def camt_statement_total(self, camt_statement):
        total = camt_statement.find(
            './{*}TxsSummry/{*}TtlNtries/{*}TtlNetNtry')
        if total is not None:
            return self._camt_amount(total)

    def camt_origin(self, camt_statement, entry):
        pool = Pool()
        Date = pool.get('ir.date')
        Origin = pool.get('account.statement.origin')

        details = entry.findall('./{*}NtryDtls/{*}TxDtls')
        if not details:
            details = [None]
        for detail in details:
            origin = Origin()
            origin.number = entry.findtext('./{*}NtryRef')
            origin.date = Date.today()
            booking_date = entry.find('./{*}BookgDt')
            if booking_date is not None:
                if booking_date.find('./{*}Dt') is not None:
                    origin.date = dt.date.fromisoformat(
                        booking_date.findtext('./{*}Dt'))
                elif booking_date.find('./{*}DtTm') is not None:
                    origin.date = dt.date.fromisoformat(
                        booking_date.findtext('./{*}DtTm')).date()
            origin.amount = self._camt_amount(entry, detail)
            origin.description = self.camt_description(camt_statement, entry)
            if detail is not None:
                origin.party = self.camt_party(camt_statement, entry, detail)
                origin.information = self.camt_information(
                    camt_statement, entry, detail)

            yield origin

    def camt_description(self, camt_statement, entry):
        return entry.findtext('./{*}AddtlNtryInf')

    def camt_party(self, camt_statement, entry, detail):
        pool = Pool()
        AccountNumber = pool.get('bank.account.number')

        path = {
            'DBIT': './{*}RltdPties/{*}CdtrAcct/{*}Id/{*}IBAN',
            'CRDT': './{*}RltdPties/{*}DbtrAcct/{*}Id/{*}IBAN',
            }[entry.findtext('./{*}CdtDbtInd')]
        number = detail.findtext(path)
        if number:
            numbers = AccountNumber.search(['OR',
                    ('number', '=', number),
                    ('number_compact', '=', number),
                    ])
            if len(numbers) == 1:
                number, = numbers
                if number.account.owners:
                    return number.account.owners[0]

    def camt_information(self, camt_statement, entre, detail):
        information = {}
        for key, path in [
                ('camt_message_identification', './{*}Refs/{*}MsgId'),
                ('camt_account_service_reference', './{*}Refs/{*}AcctSvcrRef'),
                ('camt_payment_information_identification',
                    './{*}Refs/{*}PmtInfId'),
                ('camt_instruction_identification', './{*}Refs/{*}InstrId'),
                ('camt_end_to_end_identification', './{*}Refs/{*}EndToEndId'),
                ('camt_transaction_identification', './{*}Refs/{*}TxId'),
                ('camt_mandate_identification', './{*}Refs/{*}MndtId'),
                ('camt_cheque_number', './{*}Refs/{*}ChqNb'),
                ('camt_clearing_system_reference', './{*}Refs/{*}ClrSysRef'),
                ('camt_account_owner_transaction_identification',
                    './{*}Refs/{*}AcctOwnrTxId'),
                ('camt_account_servicer_transaction_identification',
                    './{*}Refs/{*}AcctSvcrTxId'),
                ('camt_market_infrastructure_transaction_identification',
                    './{*}Refs/{*}MktInfrstrctrTxId'),
                ('camt_processing_identification', './{*}Refs/{*}PrcgId'),
                ('camt_remittance_information', './{*}RmtInf/{*}Ustrd'),
                ('camt_additional_transaction_information', './{*}AddtlTxInf'),
                ('camt_debtor_name', './{*}RltdPties/{*}Dbtr//{*}Nm'),
                ('camt_ultimate_debtor_name',
                    './{*}RltdPties/{*}UltmtDbtr//{*}Nm'),
                ('camt_debtor_iban',
                    './{*}RltdPties/{*}DbtrAcct/{*}Id/{*}IBAN'),
                ('camt_creditor_name', './{*}RltdPties/{*}Cdtr//{*}Nm'),
                ('camt_ultimate_creditor_name',
                    './{*}RltdPties/{*}UltmtCdtr//{*}Nm'),
                ('camt_creditor_iban',
                    './{*}RltdPties/{*}CdtrAcct/{*}Id/{*}IBAN'),
                ]:
            value = detail.findtext(path)
            if value:
                information[key] = value
        return information
