import time
import pprint
import locale
import decimal
import datetime

from quixote import get_request, get_response, get_session, redirect
from quixote.directory import Directory, AccessControlled
from quixote.html import TemplateIO, htmltext

import wcs
import wcs.admin.root
from wcs.formdef import FormDef

from wcs.qommon import _
from wcs.qommon import errors, misc, template, get_logger
from wcs.qommon.form import *
from wcs.qommon.admin.emails import EmailsDirectory
from wcs.qommon.backoffice.menu import html_top
from wcs.qommon import get_cfg

from .payments import (eopayment, Regie, is_payment_supported, Invoice,
        Transaction, notify_paid_invoice)

from wcs.qommon.admin.texts import TextsDirectory

if not set:
    from sets import Set as set

def invoice_as_html(invoice):
    r = TemplateIO(html=True)
    r += htmltext('<div id="invoice">')
    r += htmltext('<h2>%s</h2>') % _('Invoice: %s') % invoice.subject
    r += htmltext('<h3>%s') % _('Amount: %s') % invoice.amount
    r += htmltext(' &euro;</h3>')
    r += htmltext('<!-- DEBUG \n')
    r += 'Invoice:\n'
    r += pprint.pformat(invoice.__dict__)
    for transaction in Transaction.get_with_indexed_value('invoice_ids', invoice.id):
        r += '\nTransaction:\n'
        r += pprint.pformat(transaction.__dict__)
    r += htmltext('\n-->')
    if invoice.formdef_id and invoice.formdata_id and \
            get_session().user == invoice.user_id:
        formdef = FormDef.get(invoice.formdef_id)
        if formdef:
            formdata = formdef.data_class().get(invoice.formdata_id, ignore_errors=True)
            if formdata:
                name = _('%(form_name)s #%(formdata_id)s') % {
                        'form_name': formdata.formdef.name,
                        'formdata_id': formdata.id }
                r += htmltext('<p class="from">%s <a href="%s">%s</a></p>') % (_('From:'), formdata.get_url(), name)
    r += htmltext('<p class="regie">%s</p>') % _('Regie: %s') % Regie.get(invoice.regie_id).label
    r += htmltext('<p class="date">%s</p>') % _('Created on: %s') % misc.localstrftime(invoice.date)
    if invoice.details:
        r += htmltext('<p class="details">%s</p>') % _('Details:')
        r += htmltext('<div class="details">')
        r += htmltext(invoice.details)
        r += htmltext('</div>')
    if invoice.canceled:
        r += htmltext('<p class="canceled">')
        r += '%s' % _('canceled on %s') % misc.localstrftime(invoice.canceled_date)
        if invoice.canceled_reason:
            r += ' (%s)' % invoice.canceled_reason
        r += htmltext('</p>')
    if invoice.paid:
        r += htmltext('<p class="paid">%s</p>') % _('paid on %s') % misc.localstrftime(invoice.paid_date)
    r += htmltext('</div>')
    return r.getvalue()


class InvoicesDirectory(Directory):
    _q_exports = ['', 'multiple']

    def _q_traverse(self, path):
        if not is_payment_supported():
            raise errors.TraversalError()
        get_response().filter['bigdiv'] = 'profile'
        if get_session().user:
            # fake breadcrumb
            get_response().breadcrumb.append(('myspace/', _('My Space')))
            get_response().breadcrumb.append(('invoices/', _('Invoices')))
        return Directory._q_traverse(self, path)

    def multiple(self):
        invoice_ids = get_request().form.get('invoice')
        if type(invoice_ids) is not list:
            return redirect('%s' % invoice_ids)
        return redirect('+'.join(invoice_ids))

    def _q_lookup(self, component):
        if str('+') in component:
            invoice_ids = component.split(str('+'))
        else:
            invoice_ids = [component]
        for invoice_id in invoice_ids:
            if not Invoice.check_crc(invoice_id):
                raise errors.TraversalError()

        template.html_top(_('Invoices'))
        r = TemplateIO(html=True)
        r += TextsDirectory.get_html_text('aq-invoice')

        regies_id = set()
        for invoice_id in invoice_ids:
            try:
                invoice = Invoice.get(invoice_id)
            except KeyError:
                raise errors.TraversalError()
            r += invoice_as_html(invoice)
            if not (invoice.paid or invoice.canceled):
                regies_id.add(invoice.regie_id)

        if len(regies_id) == 1:
            r += htmltext('<p class="command">')
            r += htmltext('<a href="%s/payment/init?invoice_ids=%s">') % (
                    get_publisher().get_frontoffice_url(), component)
            if len(invoice_ids) > 1:
                r += _('Pay Selected Invoices')
            else:
                r += _('Pay')
            r += htmltext('</a></p>')
        if len(regies_id) > 1:
            r += _('You can not pay to different regies.')

        return r.getvalue()

    def _q_index(self):
        return redirect('..')


class RegieDirectory(Directory):
    _q_exports = ['', 'edit', 'delete', 'options']

    def __init__(self, regie):
        self.regie = regie

    def _q_index(self):
        html_top('payments', title = _('Regie: %s') % self.regie.label)
        r = TemplateIO(html=True)
        get_response().filter['sidebar'] = self.get_sidebar()
        r += htmltext('<h2>%s</h2>') % _('Regie: %s') % self.regie.label

        r += get_session().display_message()

        if self.regie.description:
            r += htmltext('<div class="bo-block">')
            r += htmltext('<p>')
            r += self.regie.description
            r += htmltext('</p>')
            r += htmltext('</div>')

        if self.regie.service:
            r += htmltext('<div class="bo-block">')
            url = get_publisher().get_frontoffice_url() + '/payment/back_asynchronous/'
            url += str(self.regie.id)
            r += htmltext('<p>')
            r += '%s %s' % (_('Banking Service:'), self.regie.service)
            r += htmltext(' (<a href="options">%s</a>)') % _('options')
            r += htmltext('</p>')
            r += htmltext('<p>')
            r += '%s %s' % (_('Payment notification URL:'), url)
            r += htmltext('</div>')

        r += self.invoice_listing()
        return r.getvalue()

    def get_sidebar(self):
        r = TemplateIO(html=True)
        r += htmltext('<ul>')
        r += htmltext('<li><a href="edit">%s</a></li>') % _('Edit')
        r += htmltext('<li><a href="delete">%s</a></li>') % _('Delete')
        r += htmltext('</ul>')
        return r.getvalue()

    def edit(self):
        form = self.form()
        if form.get_submit() == 'cancel':
            return redirect('.')

        if form.is_submitted() and not form.has_errors():
            self.submit(form)
            return redirect('..')

        html_top('payments', title = _('Edit Regie: %s') % self.regie.label)
        r = TemplateIO(html=True)
        r += htmltext('<h2>%s</h2>') % _('Edit Regie: %s') % self.regie.label
        r += form.render()
        return r.getvalue()


    def form(self):
        form = Form(enctype='multipart/form-data')
        form.add(StringWidget, 'label', title=_('Label'), required=True,
                value=self.regie.label)
        form.add(TextWidget, 'description', title=_('Description'),
                value=self.regie.description, rows=5, cols=60)
        form.add(SingleSelectWidget, 'service', title=_('Banking Service'),
                value=self.regie.service, required=True,
                options = [
                        ('dummy', _('Dummy (for tests)')),
                        ('sips', 'SIPS'),
                        ('systempayv2', 'systempay (Banque Populaire)'),
                        ('spplus', _('SP+ (Caisse d\'epargne)'))])
        form.add_submit('submit', _('Submit'))
        form.add_submit('cancel', _('Cancel'))
        return form

    def submit(self, form):
        for k in ('label', 'description', 'service'):
            widget = form.get_widget(k)
            if widget:
                setattr(self.regie, k, widget.parse())
        self.regie.store()

    def delete(self):
        form = Form(enctype='multipart/form-data')
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
                        'You are about to irrevocably delete this regie.')))
        form.add_submit('submit', _('Submit'))
        form.add_submit('cancel', _('Cancel'))
        if form.get_submit() == 'cancel':
            return redirect('..')
        if not form.is_submitted() or form.has_errors():
            get_response().breadcrumb.append(('delete', _('Delete')))
            r = TemplateIO(html=True)
            html_top('payments', title = _('Delete Regie'))
            r += htmltext('<h2>%s</h2>') % _('Deleting Regie: %s') % self.regie.label
            r += form.render()
            return r.getvalue()
        else:
            self.regie.remove_self()
            return redirect('..')

    def option_form(self):
        form = Form(enctype='multipart/form-data')
        module = eopayment.get_backend(self.regie.service)
        service_options = {}
        for infos in module.description['parameters']:
            if 'default' in infos:
                service_options[infos['name']] = infos['default']
        service_options.update(self.regie.service_options or {})

        banking_titles = {
            ('dummy', 'direct_notification_url'): N_('Direct Notification URL'),
            ('dummy', 'siret'): N_('Dummy SIRET'),
        }

        for infos in module.description['parameters']:
            name = infos['name']
            caption = infos.get('caption', name).encode(get_publisher().site_charset)
            title = banking_titles.get((self.regie.service, name), caption)
            kwargs = {}
            widget = StringWidget
            if infos.get('help_text') is not None:
                kwargs['hint'] = _(infos['help_text'].encode(get_publisher().site_charset))
            if infos.get('required', False):
                kwargs['required'] = True
            if infos.get('max_length') is not None:
                kwargs['size'] = infos['max_length']
            elif infos.get('length') is not None:
                kwargs['size'] = infos['length']
            else:
                kwargs['size'] = 80
            if kwargs['size'] > 100:
                widget = TextWidget
                kwargs['cols'] = 80
                kwargs['rows'] = 5
            if 'type' not in infos or infos['type'] is str:
                form.add(widget, name, title=_(title),
                         value=service_options.get(name), **kwargs)
            elif infos['type'] is bool:
                form.add(CheckboxWidget, name, title=title,
                         value=service_options.get(name), **kwargs)
        form.add_submit('submit', _('Submit'))
        form.add_submit('cancel', _('Cancel'))
        return form

    def options(self):
        r = TemplateIO(html=True)
        form = self.option_form()

        module = eopayment.get_backend(self.regie.service)
        try:
            r += htmltext('<!-- Payment backend description: \n')
            r += pprint.pformat(module.description)
            r += htmltext('-->')
        except:
            return template.error_page(_('Payment backend do not list its options'))
            raise errors.TraversalError()
        r += htmltext('<!-- \n')
        r += 'Service options\n'
        r += pprint.pformat(self.regie.service_options)
        r += htmltext('-->')

        if form.get_submit() == 'cancel':
            return redirect('.')

        if form.is_submitted() and not form.has_errors():
             if self.submit_options(form, module):
                return redirect('..')

        html_top('payments', title=_('Edit Service Options'))
        r += htmltext('<h2>%s</h2>') % _('Edit Service Options')
        r += form.render()
        return r.getvalue()

    def submit_options(self, form, module):
        # extra validation
        error = False
        for infos in module.description['parameters']:
            widget = form.get_widget(infos['name'])
            value = widget.parse()
            if value and 'validation' in infos:
                try:
                    if not infos['validation'](value):
                        widget.set_error(_('Valeur invalide'))
                        error = True
                except ValueError, e:
                    widget.set_error(_(e.message))
                    error = True
        if error:
            return False
        if not self.regie.service_options:
            self.regie.service_options = {}
        for infos in module.description['parameters']:
            name = infos['name']
            value = form.get_widget(name).parse()
            if value is None:
                value = ''
            if hasattr(value, 'strip'):
                value = value.strip()
            if infos.get('default') is not None:
                if value == infos['default']:
                    self.regie.service_options.pop(name, None)
                else:
                    self.regie.service_options[name] = form.get_widget(name).parse()
            elif not value:
                self.regie.service_options.pop(name, None)
            else:
                self.regie.service_options[name] = form.get_widget(name).parse()
        self.regie.store()
        return True

    PAGINATION = 50

    def monetary_amount(self, val):
        if not val:
            return ''
        if isinstance(val, basestring):
            val = val.replace(',', '.')
        return '%.2f' % decimal.Decimal(val)

    def get_sort_by(self):
        request = get_request()
        sort_by = request.form.get('sort_by')
        if sort_by not in ('date', 'paid_date', 'username'):
            sort_by = 'date'
        return sort_by

    def get_invoices(self):
        sort_by = self.get_sort_by()
        invoices = list(Invoice.get_with_indexed_value('regie_id', self.regie.id,
                ignore_errors=True))
        if 'date' in sort_by:
            reverse = True
            key = lambda i: getattr(i, sort_by) or datetime.datetime.now()
        else:
            reverse = False
            key = lambda i: getattr(i, sort_by) or ''
        invoices.sort(reverse=reverse, key=key)
        return invoices

    def unpay(self, request, invoice):
        get_logger().info(_('manually set unpaid invoice %(invoice_id)s in regie %(regie)s')
            % dict(invoice_id=invoice.id, regie=self.regie.id))
        transaction = Transaction()
        transaction.invoice_ids = [ invoice.id ]
        transaction.order_id = 'Manual action'
        transaction.start = datetime.datetime.now()
        transaction.end = transaction.start
        transaction.bank_data = { 
            'action': 'Set unpaid', 
            'by': request.user.get_display_name() + ' (%s)' % request.user.id
        }
        transaction.store()
        invoice.unpay()

    def pay(self, request, invoice):
        get_logger().info(_('manually set paid invoice %(invoice_id)s in regie %(regie)s')
            % dict(invoice_id=invoice.id, regie=self.regie.id))
        transaction = Transaction()
        transaction.invoice_ids = [ invoice.id ]
        transaction.order_id = 'Manual action'
        transaction.start = datetime.datetime.now()
        transaction.end = transaction.start
        transaction.bank_data = { 
            'action': 'Set paid', 
            'by': request.user.get_display_name() + ' (%s)' % request.user.id
        }
        transaction.store()
        invoice.pay()

    def invoice_listing(self):
        request = get_request()
        get_response().add_css_include('../../themes/auquotidien/admin.css')
        if request.get_method() == 'POST':
            invoice_id = request.form.get('id')
            invoice = Invoice.get(invoice_id, ignore_errors=True)
            if invoice:
                if 'unpay' in request.form:
                    self.unpay(request, invoice)
                elif 'pay' in request.form:
                    self.pay(request, invoice)
                return redirect('')
        try:
            offset = int(request.form.get('offset', 0))
        except ValueError:
            offset = 0
        r = TemplateIO(html=True)
        r += htmltext('<table id="invoice-listing" borderspacing="0">')
        r += htmltext('<thead>')
        r += htmltext('<tr>')
        r += htmltext('<td><a href="?sort_by=date&offset=%d">Creation</a></td>') % offset
        r += htmltext('<td>Amount</td>')
        r += htmltext('<td><a href="?sort_by=paid_date&offset=%d">Paid</a></td>') % offset
        r += htmltext('<td><a href="?sort_by=username&offset=%d">User</a></td>') % offset
        r += htmltext('<td>Titre</td>')
        r += htmltext('<td></td>')
        r += htmltext('</tr>')
        r += htmltext('</thead>')
        invoices = self.get_invoices()
        for invoice in invoices[offset:offset+self.PAGINATION]:
            r += htmltext('<tbody class="invoice-rows">')
            r += htmltext('<tr class="invoice-row"><td>')
            r += misc.localstrftime(invoice.date)
            r += htmltext('</td><td class="amount">')
            r += self.monetary_amount(invoice.amount)
            r += htmltext('</td><td>')
            if invoice.paid:
                r += misc.localstrftime(invoice.paid_date)
            else:
                r += ''
            r += htmltext('</td><td>')
            user = invoice.get_user()
            if user:
                r += user.name
            r += htmltext('</td><td class="subject">%s</td>') % (invoice.subject or '')
            r += htmltext('<td>')
            r += htmltext('<form method="post">')
            r += htmltext('<input type="hidden" name="id" value="%s"/> ') % invoice.id
            if invoice.paid:
                r += htmltext('<input type="submit" name="unpay" value="%s"/>') % _('Set unpaid')
            else:
                r += htmltext('<input type="submit" name="pay" value="%s"/>') % _('Set paid')
            r += htmltext('</form>')

            r += htmltext('</td></tr>')
            transactions = list(Transaction.get_with_indexed_value('invoice_ids',
                    invoice.id))
            for transaction in sorted(transactions, key=lambda x: x.start):
                r += htmltext('<tr>')
                r += htmltext('<td></td>')
                r += htmltext('<td colspan="5">')
                r += 'OrderID: %s' % transaction.order_id
                r += ' Start: %s' % transaction.start
                if transaction.end:
                    r += ' End: %s' % transaction.end
                if transaction.bank_data:
                    r += ' Bank data: %r' % transaction.bank_data
                r += htmltext('</td>')
                r += htmltext('</tr>')
            r += htmltext('</tbody>')
        r += htmltext('</tbody></table>')
        if offset != 0:
            r += htmltext('<a href="?offset=%d>%s</a> ') % (
                 max(0, offset-self.PAGINATION), _('Previous'))
        if offset + self.PAGINATION < len(invoices):
            r += htmltext('<a href="?offset=%d>%s</a> ') % (
                 max(0, offset-self.PAGINATION), _('Previous'))
        return r.getvalue()


class RegiesDirectory(Directory):
    _q_exports = ['', 'new']

    def _q_traverse(self, path):
        get_response().breadcrumb.append(('regie/', _('Regies')))
        return Directory._q_traverse(self, path)

    def _q_index(self):
        return redirect('..')

    def new(self):
        regie_ui = RegieDirectory(Regie())

        form = regie_ui.form()
        if form.get_submit() == 'cancel':
            return redirect('.')

        if form.is_submitted() and not form.has_errors():
            regie_ui.submit(form)
            return redirect('%s/' % regie_ui.regie.id)

        get_response().breadcrumb.append(('new', _('New Regie')))
        html_top('payments', title = _('New Regie'))
        r = TemplateIO(html=True)
        r += htmltext('<h2>%s</h2>') % _('New Regie')
        r += form.render()
        return r.getvalue()

    def _q_lookup(self, component):
        try:
            regie = Regie.get(component)
        except KeyError:
            raise errors.TraversalError()
        get_response().breadcrumb.append((str(regie.id), regie.label))
        return RegieDirectory(regie)


class PaymentsDirectory(AccessControlled, Directory):
    _q_exports = ['', 'regie']
    label = N_('Payments')

    regie = RegiesDirectory()

    def is_accessible(self, user):
        from .backoffice import check_visibility
        return check_visibility('payments', user)

    def _q_access(self):
        user = get_request().user
        if not user:
            raise errors.AccessUnauthorizedError()

        if not self.is_accessible(user):
            raise errors.AccessForbiddenError(
                    public_msg = _('You are not allowed to access Payments Management'),
                    location_hint = 'backoffice')

        get_response().breadcrumb.append(('payments/', _('Payments')))

    def _q_index(self):
        html_top('payments', _('Payments'))
        get_response().filter['sidebar'] = self.get_sidebar()
        r = TemplateIO(html=True)

        if not is_payment_supported:
            r += htmltext('<p class="infonotice">')
            r += _('Payment is not supported.')
            r += htmltext('</p>')

        regies = Regie.select()
        r += htmltext('<h2>%s</h2>') % _('Regies')
        if not regies:
            r += htmltext('<p>')
            r += _('There are no regies defined at the moment.')
            r += htmltext('</p>')
        r += htmltext('<ul class="biglist" id="regies-list">')
        for l in regies:
            regie_id = l.id
            r += htmltext('<li class="biglistitem" id="itemId_%s">') % regie_id
            r += htmltext('<strong class="label"><a href="regie/%s/">%s</a></strong>') % (regie_id, l.label)
            r += htmltext('</li>')
        r += htmltext('</ul>')
        return r.getvalue()

    def get_sidebar(self):
        r = TemplateIO(html=True)
        r += htmltext('<ul id="sidebar-actions">')
        r += htmltext('  <li><a class="new-item" href="regie/new">%s</a></li>') % _('New Regie')
        r += htmltext('</ul>')
        return r.getvalue()


TextsDirectory.register('aq-invoice',
        N_('Message on top of an invoice'),
        category = N_('Invoices'))

EmailsDirectory.register('payment-new-invoice-email',
        N_('New invoice'),
        N_('Available variables: user, regie, invoice, invoice_url'),
        category = N_('Invoices'),
        default_subject = N_('New invoice'),
        default_body = N_('''
A new invoice is available at [invoice_url].
'''))

EmailsDirectory.register('payment-invoice-paid-email',
        N_('Paid invoice'),
        N_('Available variables: user, regie, invoice, invoice_url'),
        category = N_('Invoices'),
        default_subject = N_('Paid invoice'),
        default_body = N_('''
The invoice [invoice_url] has been paid.
'''))

EmailsDirectory.register('payment-invoice-canceled-email',
        N_('Canceled invoice'),
        N_('Available variables: user, regie, invoice, invoice_url'),
        category = N_('Invoices'),
        default_subject = N_('Canceled invoice'),
        default_body = N_('''
The invoice [invoice.id] has been canceled.
'''))
