# -*- encoding: utf-8 -*-
##############################################################################
#
#    Odoo, Open Source Business Applications
#    Copyright (C) 2014-2015 Odoo S.A. <http://www.odoo.com>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import base64
import xml.etree.ElementTree as ET
from collections import namedtuple
from datetime import datetime

from openerp import exceptions, SUPERUSER_ID, tools
from openerp.osv import fields, osv
from openerp.tools.translate import _

INTRASTAT_XMLNS = 'http://www.onegate.eu/2010-01-01'

class xml_decl(osv.TransientModel):
    """
    Intrastat XML Declaration
    """
    _name = "l10n_be_intrastat_xml.xml_decl"
    _description = 'Intrastat XML Declaration'

    def _get_tax_code(self, cr, uid, context=None):
        obj_tax_code = self.pool.get('account.tax.code')
        obj_user = self.pool.get('res.users')
        company_id = obj_user.browse(cr, uid, uid, context=context).company_id.id
        tax_code_ids = obj_tax_code.search(cr, uid, [('company_id', '=', company_id),
                                                     ('parent_id', '=', False)],
                                           context=context)
        return tax_code_ids and tax_code_ids[0] or False

    def _get_def_monthyear(self, cr, uid, context=None):
        td = datetime.strptime(fields.date.context_today(self, cr, uid, context=context),
                               tools.DEFAULT_SERVER_DATE_FORMAT).date()
        return td.strftime('%Y'), td.strftime('%m')

    def _get_def_month(self, cr, uid, context=None):
        return self._get_def_monthyear(cr, uid, context=context)[1]

    def _get_def_year(self, cr, uid, context=None):
        return self._get_def_monthyear(cr, uid, context=context)[0]

    _columns = {
        'name': fields.char('File Name'),
        'month': fields.selection([('01','January'), ('02','February'), ('03','March'),
                                   ('04','April'), ('05','May'), ('06','June'), ('07','July'),
                                   ('08','August'), ('09','September'), ('10','October'),
                                   ('11','November'), ('12','December')], 'Month', required=True),
        'year': fields.char('Year', size=4, required=True),
        'tax_code_id': fields.many2one('account.tax.code', 'Company Tax Chart',
                                       domain=[('parent_id', '=', False)], required=True),
        'arrivals': fields.selection([('be-exempt', 'Exempt'),
                                      ('be-standard', 'Standard'),
                                      ('be-extended', 'Extended')],
                                     'Arrivals', required=True),
        'dispatches': fields.selection([('be-exempt', 'Exempt'),
                                      ('be-standard', 'Standard'),
                                      ('be-extended', 'Extended')],
                                       'Dispatches', required=True),
        'file_save': fields.binary('Intrastat Report File', readonly=True),
        'state': fields.selection([('draft', 'Draft'), ('download', 'Download')], string="State"),
    }

    _defaults = {
        'arrivals': 'be-standard',
        'dispatches': 'be-standard',
        'name': 'intrastat.xml',
        'tax_code_id': _get_tax_code,
        'month': _get_def_month,
        'year': _get_def_year,
        'state': 'draft',
    }
    
    def _company_warning(self, cr, uid, translated_msg, context=None):
        """ Raise a error with custom message, asking user to configure company settings """
        xmlid_mod = self.pool['ir.model.data']
        action_id = xmlid_mod.xmlid_to_res_id(cr, uid, 'base.action_res_company_form')
        raise exceptions.RedirectWarning(
            translated_msg, action_id, _('Go to company configuration screen'))
            
    def create_xml(self, cr, uid, ids, context=None):
        """Creates xml that is to be exported and sent to estate for partner vat intra.
        :return: Value for next action.
        :rtype: dict
        """
        decl_datas = self.browse(cr, uid, ids[0])
        company = decl_datas.tax_code_id.company_id
        if not (company.partner_id and company.partner_id.country_id and
                company.partner_id.country_id.id):
            self._company_warning(
                cr, uid,
                _('The country of your company is not set, '
                  'please make sure to configure it first.'),
                context=context)
        kbo = company.company_registry
        if not kbo:
            self._company_warning(
                cr, uid,
                _('The registry number of your company is not set, '
                  'please make sure to configure it first.'),
                context=context)
        if len(decl_datas.year) != 4:
            raise exceptions.Warning(_('Year must be 4 digits number (YYYY)'))

        #Create root declaration
        decl = ET.Element('DeclarationReport')
        decl.set('xmlns', INTRASTAT_XMLNS)

        #Add Administration elements
        admin = ET.SubElement(decl, 'Administration')
        fromtag = ET.SubElement(admin, 'From')
        fromtag.text = kbo
        fromtag.set('declarerType', 'KBO')
        ET.SubElement(admin, 'To').text = "NBB"
        ET.SubElement(admin, 'Domain').text = "SXX"
        if decl_datas.arrivals == 'be-standard':
            decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
                                        dispatchmode=False, extendedmode=False, context=context))
        elif decl_datas.arrivals == 'be-extended':
            decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
                                        dispatchmode=False, extendedmode=True, context=context))
        if decl_datas.dispatches == 'be-standard':
            decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
                                        dispatchmode=True, extendedmode=False, context=context))
        elif decl_datas.dispatches == 'be-extended':
            decl.append(self._get_lines(cr, SUPERUSER_ID, ids, decl_datas, company,
                                        dispatchmode=True, extendedmode=True, context=context))

        #Get xml string with declaration
        data_file = ET.tostring(decl, encoding='UTF-8', method='xml')

        #change state of the wizard
        self.write(cr, uid, ids,
                   {'name': 'intrastat_%s%s.xml' % (decl_datas.year, decl_datas.month),
                    'file_save': base64.encodestring(data_file),
                    'state': 'download'},
                   context=context)
        return {
            'name': _('Save'),
            'context': context,
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'l10n_be_intrastat_xml.xml_decl',
            'type': 'ir.actions.act_window',
            'target': 'new',
            'res_id': ids[0],
        }

    def _get_lines(self, cr, uid, ids, decl_datas, company, dispatchmode=False,
                   extendedmode=False, context=None):
        intrastatcode_mod = self.pool['report.intrastat.code']
        invoiceline_mod = self.pool['account.invoice.line']
        product_mod = self.pool['product.product']
        region_mod = self.pool['l10n_be_intrastat.region']
        warehouse_mod = self.pool['stock.warehouse']

        if dispatchmode:
            mode1 = 'out_invoice'
            mode2 = 'in_refund'
            declcode = "29"
        else:
            mode1 = 'in_invoice'
            mode2 = 'out_refund'
            declcode = "19"

        decl = ET.Element('Report')
        if not extendedmode:
            decl.set('code', 'EX%sS' % declcode)
        else:
            decl.set('code', 'EX%sE' % declcode)
        decl.set('date', '%s-%s' % (decl_datas.year, decl_datas.month))
        datas = ET.SubElement(decl, 'Data')
        if not extendedmode:
            datas.set('form', 'EXF%sS' % declcode)
        else:
            datas.set('form', 'EXF%sE' % declcode)
        datas.set('close', 'true')
        intrastatkey = namedtuple("intrastatkey",
                                  ['EXTRF', 'EXCNT', 'EXTTA', 'EXREG',
                                   'EXGO', 'EXTPC', 'EXDELTRM'])
        entries = {}

        sqlreq = """
            select
                inv_line.id
            from
                account_invoice_line inv_line
                join account_invoice inv on inv_line.invoice_id=inv.id
                left join res_country on res_country.id = inv.intrastat_country_id
                left join res_partner on res_partner.id = inv.partner_id
                left join res_country countrypartner on countrypartner.id = res_partner.country_id
                join product_product on inv_line.product_id=product_product.id
                join product_template on product_product.product_tmpl_id=product_template.id
            where
                inv.state in ('open','paid')
                and inv.company_id=%s
                and not product_template.type='service'
                and (res_country.intrastat=true or (inv.intrastat_country_id is null
                                                    and countrypartner.intrastat=true))
                and ((res_country.code is not null and not res_country.code=%s)
                     or (res_country.code is null and countrypartner.code is not null
                     and not countrypartner.code=%s))
                and inv.type in (%s, %s)
                and to_char(inv.date_invoice, 'YYYY')=%s
                and to_char(inv.date_invoice, 'MM')=%s
            """

        cr.execute(sqlreq, (company.id, company.partner_id.country_id.code,
                            company.partner_id.country_id.code, mode1, mode2,
                            decl_datas.year, decl_datas.month))
        lines = cr.fetchall()
        invoicelines_ids = [rec[0] for rec in lines]
        invoicelines = invoiceline_mod.browse(cr, uid, invoicelines_ids, context=context)
        for inv_line in invoicelines:

            #Check type of transaction
            if inv_line.invoice_id.intrastat_transaction_id:
                extta = inv_line.invoice_id.intrastat_transaction_id.code
            else:
                extta = "1"
            #Check country
            if inv_line.invoice_id.intrastat_country_id:
                excnt = inv_line.invoice_id.intrastat_country_id.code
            else:
                excnt = inv_line.invoice_id.partner_id.country_id.code

            #Check region
            #If purchase, comes from purchase order, linked to a location,
            #which is linked to the warehouse
            #if sales, the sale order is linked to the warehouse
            #if sales, from a delivery order, linked to a location,
            #which is linked to the warehouse
            #If none found, get the company one.
            exreg = None
            if inv_line.invoice_id.type in ('in_invoice', 'in_refund'):
                #comes from purchase
                POL = self.pool['purchase.order.line']
                poline_ids = POL.search(
                    cr, uid, [('invoice_lines', 'in', inv_line.id)], context=context)
                if poline_ids:
                    purchaseorder = POL.browse(cr, uid, poline_ids[0], context=context).order_id
                    region_id = warehouse_mod.get_regionid_from_locationid(
                        cr, uid, purchaseorder.location_id.id, context=context)
                    if region_id:
                        exreg = region_mod.browse(cr, uid, region_id).code
            elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'):
                #comes from sales
                soline_ids = self.pool['sale.order.line'].search(
                    cr, uid, [('invoice_lines', 'in', inv_line.id)], context=context)
                if soline_ids:
                    saleorder = self.pool['sale.order.line'].browse(
                        cr, uid, soline_ids[0], context=context).order_id
                    if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id:
                        exreg = region_mod.browse(
                            cr, uid, saleorder.warehouse_id.region_id.id, context=context).code

            if not exreg:
                if company.region_id:
                    exreg = company.region_id.code
                else:
                    self._company_warning(
                        cr, uid,
                        _('The Intrastat Region of the selected company is not set, '
                          'please make sure to configure it first.'),
                        context=context)

            #Check commodity codes
            intrastat_id = product_mod.get_intrastat_recursively(
                cr, uid, inv_line.product_id.id, context=context)
            if intrastat_id:
                exgo = intrastatcode_mod.browse(cr, uid, intrastat_id, context=context).name
            else:
                raise exceptions.Warning(
                    _('Product "%s" has no intrastat code, please configure it') %
                        inv_line.product_id.display_name)

            #In extended mode, 2 more fields required
            if extendedmode:
                #Check means of transport
                if inv_line.invoice_id.transport_mode_id:
                    extpc = inv_line.invoice_id.transport_mode_id.code
                elif company.transport_mode_id:
                    extpc = company.transport_mode_id.code
                else:
                    self._company_warning(
                        cr, uid,
                        _('The default Intrastat transport mode of your company '
                          'is not set, please make sure to configure it first.'),
                        context=context)

                #Check incoterm
                if inv_line.invoice_id.incoterm_id:
                    exdeltrm = inv_line.invoice_id.incoterm_id.code
                elif company.incoterm_id:
                    exdeltrm = company.incoterm_id.code
                else:
                    self._company_warning(
                        cr, uid,
                        _('The default Incoterm of your company is not set, '
                          'please make sure to configure it first.'),
                        context=context)
            else:
                extpc = ""
                exdeltrm = ""
            linekey = intrastatkey(EXTRF=declcode, EXCNT=excnt,
                                   EXTTA=extta, EXREG=exreg, EXGO=exgo,
                                   EXTPC=extpc, EXDELTRM=exdeltrm)
            #We have the key
            #calculate amounts
            if inv_line.price_unit and inv_line.quantity:
                amount = inv_line.price_unit * inv_line.quantity
            else:
                amount = 0
            if (not inv_line.uos_id.category_id
                    or not inv_line.product_id.uom_id.category_id
                    or inv_line.uos_id.category_id.id != inv_line.product_id.uom_id.category_id.id):
                weight = inv_line.product_id.weight_net * inv_line.quantity
            else:
                weight = (inv_line.product_id.weight_net *
                          inv_line.quantity * inv_line.uos_id.factor)
            if (not inv_line.uos_id.category_id or not inv_line.product_id.uom_id.category_id
                    or inv_line.uos_id.category_id.id != inv_line.product_id.uom_id.category_id.id):
                supply_units = inv_line.quantity
            else:
                supply_units = inv_line.quantity * inv_line.uos_id.factor
            amounts = entries.setdefault(linekey, (0, 0, 0))
            amounts = (amounts[0] + amount, amounts[1] + weight, amounts[2] + supply_units)
            entries[linekey] = amounts

        numlgn = 0
        for linekey in entries:
            numlgn += 1
            amounts = entries[linekey]
            item = ET.SubElement(datas, 'Item')
            self._set_Dim(item, 'EXSEQCODE', unicode(numlgn))
            self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF))
            self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT))
            self._set_Dim(item, 'EXTTA', unicode(linekey.EXTTA))
            self._set_Dim(item, 'EXREG', unicode(linekey.EXREG))
            self._set_Dim(item, 'EXTGO', unicode(linekey.EXGO))
            if extendedmode:
                self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC))
                self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM))
            self._set_Dim(item, 'EXTXVAL', unicode(round(amounts[0], 0)).replace(".", ","))
            self._set_Dim(item, 'EXWEIGHT', unicode(round(amounts[1], 0)).replace(".", ","))
            self._set_Dim(item, 'EXUNITS', unicode(round(amounts[2], 0)).replace(".", ","))

        if numlgn == 0:
            #no datas
            datas.set('action', 'nihil')
        return decl

    def _set_Dim(self, item, prop, value):
        dim = ET.SubElement(item, 'Dim')
        dim.set('prop', prop)
        dim.text = value
