import sys
from datetime import datetime
import collections
from decimal import Decimal

from wcs.qommon import _
from wcs.qommon.cron import CronJob
from wcs.qommon.publisher import get_publisher_class
from wcs.qommon import get_logger

from wcs.users import User

from .abelium_domino_ui import (get_client, is_activated, get_invoice_regie,
        abelium_domino_ws)
from .payments import Invoice, Transaction

DOMINO_ID_PREFIX = 'DOMINO-'

def synchronize_domino(publisher):
    regie = get_invoice_regie(publisher)
    logger = get_logger()
    if not is_activated(publisher) or not regie:
        return
    client = get_client(publisher)
    if client is None:
        logger.warning('Unable to create a DominoWS object')
        return
    client.clear_cache()
    users = User.values()
    users_by_mail = dict(((user.email, user) for user in users))
    users_by_code_interne = {}
    for user in users:
        if hasattr(user, 'abelium_domino_code_famille'):
            users_by_code_interne[user.abelium_domino_code_famille] = user
    try:
        invoices = client.invoices
    except abelium_domino_ws.DominoException, e:
        publisher.notify_of_exception(sys.exc_info(), context='[DOMINO]')
        logger.error('domino cron: failure to retrieve invoice list from domino '
                'for synchronization [error:%s]', e)
        return
    # import new invoices
    logger.info('domino cron: retrieved %i invoices', len(invoices))
    for invoice_id, invoice in invoices.iteritems():
        user = None
        if invoice.family.code_interne in users_by_code_interne:
            user = users_by_code_interne[invoice.family.code_interne]
        if user is None:
            for email in (invoice.family.email_pere, invoice.family.email_mere,
                    invoice.family.adresse_internet):
                user = users_by_mail.get(email)
                if user:
                    break
            else:
                continue
        external_id = '%s%s' % (DOMINO_ID_PREFIX, invoice.id)
        payment_invoice = Invoice.get_on_index(external_id, 'external_id', ignore_errors=True)
        if payment_invoice:
            continue
        if invoice.reste_du == Decimal(0) or invoice.reste_du < Decimal(0):
            continue
        payment_invoice = Invoice()
        payment_invoice.user_id = user.id
        payment_invoice.external_id = external_id
        payment_invoice.regie_id = regie.id
        payment_invoice.formdef_id = None
        payment_invoice.formdata_id = None
        payment_invoice.amount = invoice.reste_du
        payment_invoice.date = invoice.creation
        payment_invoice.domino_synchro_date = datetime.now()
        if 'etablissement' in invoice._detail:
            etablissement = invoice._detail['etablissement'].encode('utf-8')
            payment_invoice.subject = _('%s - Childcare services') % etablissement
        else:
            payment_invoice.subject = _('Childcare services')
        if invoice._detail.get('lignes'):
            details = []
            details.append('<table class="invoice-details"><thead>')
            tpl = '''<tr>
    <td>%(designation)s</td>
    <td>%(quantite)s</td>
    <td>%(prix)s</td>
    <td>%(montant)s</td>
</tr>'''
            captions = {
                    'designation': _('Caption'),
                    'quantite': _('Quantity'),
                    'prix': _('Price'),
                    'amount': _('Amount')
            }
            details.append(tpl % captions)
            details.append('</thead>')
            details.append('<tbody>')
            for ligne in invoice._detail['lignes']:
                def encode(x):
                    a, b = x
                    b = b.encode('utf-8')
                    return (a,b)
                ligne = map(encode, ligne)
                ligne = dict(ligne)
                base = collections.defaultdict(lambda:'')
                base.update(ligne)
                details.append(tpl % base)
            details.append('</tbody></table>')
            payment_invoice.details = '\n'.join(details)
        payment_invoice.store()
        logger.info('domino cron: remote invoice %s for family %s added to user %s invoices with id %s',
                invoice.id, invoice.family.id, user.id, payment_invoice.id)

    # update invoices
    invoices_ids = dict(invoices.iteritems())
    for payment_invoice in Invoice.values():
        if payment_invoice.external_id is None or not payment_invoice.external_id.startswith(DOMINO_ID_PREFIX):
            continue # not a payment related to domino we skip
        i = payment_invoice.external_id[len(DOMINO_ID_PREFIX):]
        i = int(i)
        invoice = invoices_ids.get(i)
        if payment_invoice.paid:
            if not invoice: 
                # invoice has been paid (locally or not) but remote invoice has
                # been deleted do, we do nothing.
                continue
            if getattr(payment_invoice, 'domino_knows_its_paid', None) or getattr(payment_invoice, 'paid_by_domino', None):
                # synchronization of payment already done, skip
                continue
            transactions = list(Transaction.get_with_indexed_value('invoice_ids',
                                                                   payment_invoice.id))
            if not transactions:
                logger.warning("domino cron: invoice %s is marked paid but does "
                        "not have any linked transaction.", payment_invoice.id)
                details = '' # no details about the payment, problem
            else:
                details = repr(transactions[0].__dict__)
            if invoice.montant != payment_invoice.amount:
                pass # add warning logs
            try:
                client.pay_invoice([invoice], invoice.montant, details,
                        payment_invoice.paid_date)
            except abelium_domino_ws.DominoException, e:
                logger.error('domino cron: invoice %s has been paid, but the remote system '
                        'is unreachable, notification will be done again '
                        'later [error: %s]', invoice.id, e)
            else:
                # memorize the date of synchronization
                payment_invoice.domino_knows_its_paid = datetime.now()
                payment_invoice.store()
                logger.info('domino cron: domino: invoice %s has been paid; remote system has been '
                        'notified', payment_invoice.id)
        else: # unpaid
            if not invoice:
                logger.info('domino cron: remote invoice %s disapearred, so its '
                        'still-unpaid local counterpart invoice %s was deleted.',
                        i, payment_invoice.id)
                payment_invoice.remove_self()
            elif invoice.paid():
                payment_invoice.paid_by_domino = True
                payment_invoice.pay()
                logger.info('domino cron: remote invoice %s has beend paid, '
                        'local invoice %s of user %s is now marked as paid.',
                        invoice.id, payment_invoice.id, payment_invoice.user_id)
            else: # not invoice.paid()
                pass # still waiting for the payment

get_publisher_class().register_cronjob(CronJob(function=synchronize_domino,
    hours=range(0, 24), minutes=range(0, 60, 30)))
