try:
    import lasso
except ImportError:
    pass

import json

from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session
from quixote.directory import AccessControlled, Directory
from quixote.html import TemplateIO, htmltext
from quixote.util import StaticFile, FileStream

from wcs.qommon import _
from wcs.qommon import template
from wcs.qommon.form import *
from wcs.qommon import get_cfg, get_logger
from wcs.qommon import errors
from wcs.api import get_user_from_api_query_string

import wcs.qommon.ident.password
from wcs.qommon.ident.password_accounts import PasswordAccount

from wcs.qommon.admin.texts import TextsDirectory

from wcs.formdef import FormDef
import wcs.myspace

from .announces import AnnounceSubscription
from .strongbox import StrongboxItem, StrongboxType
from .payments import Invoice, Regie, is_payment_supported

class MyInvoicesDirectory(Directory):
    _q_exports = ['']

    def _q_traverse(self, path):
        if not is_payment_supported():
            raise errors.TraversalError()
        get_response().breadcrumb.append(('invoices/', _('Invoices')))
        return Directory._q_traverse(self, path)

    def _q_index(self):
        user = get_request().user
        if not user or user.anonymous:
            raise errors.AccessUnauthorizedError()

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

        r += get_session().display_message()

        invoices = []
        invoices.extend(Invoice.get_with_indexed_value(
            str('user_id'), str(user.id)))

        def cmp_invoice(a, b):
            t = cmp(a.regie_id, b.regie_id)
            if t != 0:
                return t
            return -cmp(a.date, b.date)

        invoices.sort(cmp_invoice)

        last_regie_id = None
        unpaid = False
        for invoice in invoices:
            if invoice.regie_id != last_regie_id:
                if last_regie_id:
                    r += htmltext('</ul>')
                    if unpaid:
                        r += htmltext('<input type="submit" value="%s"/>') % _('Pay Selected Invoices')
                    r += htmltext('</form>')
                last_regie_id = invoice.regie_id
                r += htmltext('<h3>%s</h3>') % Regie.get(last_regie_id).label
                unpaid = False
                r += htmltext('<form action="%s/invoices/multiple">' % get_publisher().get_frontoffice_url())
                r += htmltext('<ul>')

            r += htmltext('<li>')
            if not (invoice.paid or invoice.canceled):
                r += htmltext('<input type="checkbox" name="invoice" value="%s"/>' % invoice.id)
                unpaid = True
            r += misc.localstrftime(invoice.date)
            r += ' - '
            r += '%s' % invoice.subject
            r += ' - '
            r += '%s' % invoice.amount
            r += htmltext(' &euro;')
            r += ' - '
            button = '<span class="paybutton">%s</span>' % _('Pay')
            if invoice.canceled:
                r += _('canceled on %s') % misc.localstrftime(invoice.canceled_date)
                r += ' - '
                button = _('Details')
            if invoice.paid:
                r += _('paid on %s') % misc.localstrftime(invoice.paid_date)
                r += ' - '
                button = _('Details')
            r += htmltext('<a href="%s/invoices/%s">%s</a>' % (get_publisher().get_frontoffice_url(),
                    invoice.id, button))
            r += htmltext('</li>')

        if last_regie_id:
            r += htmltext('</ul>')
            if unpaid:
                r += htmltext('<input type="submit" value="%s"/>') % _('Pay Selected Invoices')
            r += htmltext('</form>')

        return r.getvalue()


class StrongboxDirectory(Directory):
    _q_exports = ['', 'add', 'download', 'remove', 'pick', 'validate']

    def _q_traverse(self, path):
        if not get_cfg('misc', {}).get('aq-strongbox'):
            raise errors.TraversalError()
        get_response().breadcrumb.append(('strongbox/', _('Strongbox')))
        return Directory._q_traverse(self, path)

    def get_form(self):
        types = [(x.id, x.label) for x in StrongboxType.select()]
        form = Form(action='add', enctype='multipart/form-data')
        form.add(StringWidget, 'description', title=_('Description'), size=60)
        form.add(FileWidget, 'file', title=_('File'), required=True)
        form.add(SingleSelectWidget, 'type_id', title=_('Document Type'),
                 options = [(None, _('Not specified'))] + types)
        form.add(DateWidget, 'date_time', title = _('Document Date'))
        form.add_submit('submit', _('Upload'))
        return form

    def _q_index(self):
        template.html_top(_('Strongbox'))
        r = TemplateIO(html=True)

        # TODO: a paragraph of explanations here could be useful

        sffiles = list(StrongboxItem.get_with_indexed_value(
                        str('user_id'), str(get_request().user.id)))
        if sffiles:
            r += htmltext('<table id="strongbox-items">')
            r += htmltext('<tr><th></th><th>%s</th><th>%s</th><th></th></tr>') % (
                _('Type'), _('Expiration'))
        else:
            r += htmltext('<p>')
            r += _('There is currently nothing in your strongbox.')
            r += htmltext('</p>')
        has_items_to_validate = False
        for i, sffile in enumerate(sffiles):
            expired = False
            if not sffile.validated_time:
                has_items_to_validate = True
                continue
            if sffile.expiration_time and sffile.expiration_time < time.localtime():
                expired = True
            if i%2:
                classnames = ['odd']
            else:
                classnames = ['even']
            if expired:
                classnames.append('expired')
            r += htmltext('<tr class="%s">') % ' '.join(classnames)
            r += htmltext('<td class="label">')
            r += sffile.get_display_name()
            r += htmltext('</td>')
            if sffile.type_id:
                r += htmltext('<td class="type">%s</td>') % StrongboxType.get(sffile.type_id).label
            else:
                r += htmltext('<td class="type">-</td>')
            if sffile.expiration_time:
                r += htmltext('<td class="expiration">%s') % strftime(misc.date_format(), sffile.expiration_time)
                if expired:
                    r += ' (%s)' % _('expired')
                r += htmltext('</td>')
            else:
                r += htmltext('<td class="expiration">-</td>')
            r += htmltext('<td class="actions">')
            r += htmltext(' [<a href="download?id=%s">%s</a>] ') % (sffile.id, _('download'))
            r += htmltext('[<a rel="popup" href="remove?id=%s">%s</a>] ') % (sffile.id, _('remove'))
            r += htmltext('</td>')
            r += htmltext('</tr>')

        if has_items_to_validate:
            r += htmltext('<tr><td colspan="4"><h3>%s</h3></td></tr>') % _('Proposed Items')
            for sffile in sffiles:
                if sffile.validated_time:
                    continue
                if sffile.expiration_time and sffile.expiration_time < time.localtime():
                    expired = True
                if i%2:
                    classnames = ['odd']
                else:
                    classnames = ['even']
                if expired:
                    classnames.append('expired')
                r += htmltext('<tr class="%s">') % ' '.join(classnames)

                r += htmltext('<td class="label">')
                r += sffile.get_display_name()
                r += htmltext('</td>')
                if sffile.type_id:
                    r += htmltext('<td class="type">%s</td>') % StrongboxType.get(sffile.type_id).label
                else:
                    r += htmltext('<td class="type">-</td>')

                if sffile.expiration_time:
                    r += htmltext('<td class="expiration">%s') % strftime(misc.date_format(), sffile.expiration_time)
                    if expired:
                        r += ' (%s)' % _('expired')
                    r += htmltext('</td>')
                else:
                    r += htmltext('<td class="expiration">-</td>')
                r += htmltext('<td class="actions">')
                r += htmltext(' [<a href="download?id=%s">%s</a>] ') % (sffile.id, _('download'))
                r += htmltext(' [<a href="validate?id=%s">%s</a>] ') % (sffile.id, _('validate'))
                r += htmltext(' [<a href="remove?id=%s">%s</a>] ') % (sffile.id, _('reject'))
                r += htmltext('</td>')
                r += htmltext('</tr>')
        if sffiles:
            r += htmltext('</table>')

        r += htmltext('<h3>%s</h3>') % _('Add a file to the strongbox')
        form = self.get_form()
        r += form.render()
        return r.getvalue()

    def add(self):
        form = self.get_form()
        if not form.is_submitted():
            if get_request().form.get('mode') == 'pick':
                return redirect('pick')
            else:
                return redirect('.')

        sffile = StrongboxItem()
        sffile.user_id = get_request().user.id
        sffile.description = form.get_widget('description').parse()
        sffile.validated_time = time.localtime()
        sffile.type_id = form.get_widget('type_id').parse()
        v = form.get_widget('date_time').parse()
        sffile.set_expiration_time_from_date(v)
        sffile.store()
        sffile.set_file(form.get_widget('file').parse())
        sffile.store()
        if get_request().form.get('mode') == 'pick':
            return redirect('pick')
        else:
            return redirect('.')

    def download(self):
        id = get_request().form.get('id')
        if not id:
            raise errors.TraversalError()
        try:
            sffile = StrongboxItem.get(id)
        except KeyError:
            raise errors.TraversalError()
        if str(sffile.user_id) != str(get_request().user.id):
            raise errors.TraversalError()

        filename = sffile.file.filename
        fd = file(filename)
        size = os.path.getsize(filename)
        response = get_response()
        response.set_content_type('application/octet-stream')
        response.set_header('content-disposition', 'attachment; filename="%s"' % sffile.file.base_filename)
        return FileStream(fd, size)

    def validate(self):
        id = get_request().form.get('id')
        if not id:
            raise errors.TraversalError()
        try:
            sffile = StrongboxItem.get(id)
        except KeyError:
            raise errors.TraversalError()
        if str(sffile.user_id) != str(get_request().user.id):
            raise errors.TraversalError()
        sffile.validated_time = time.time()
        sffile.store()
        return redirect('.')

    def remove(self):
        id = get_request().form.get('id')
        if not id:
            raise errors.TraversalError()
        try:
            sffile = StrongboxItem.get(id)
        except KeyError:
            raise errors.TraversalError()
        if str(sffile.user_id) != str(get_request().user.id):
            raise errors.TraversalError()

        r = TemplateIO(html=True)
        form = Form(enctype='multipart/form-data')
        form.add_hidden('id', get_request().form.get('id'))
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
                        'You are about to irrevocably delete this item from your strongbox.')))
        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():
            if sffile.type_id:
                r += htmltext('<h2>%s</h2>') % _('Deleting %(filetype)s: %(filename)s') % {
                        'filetype': StrongboxType.get(sffile.type_id).label,
                        'filename': sffile.get_display_name()
                    }
            else:
                r += htmltext('<h2>%s</h2>') % _('Deleting %(filename)s') % {'filename': sffile.get_display_name()}
            r += form.render()
            return r.getvalue()
        else:
            sffile.remove_self()
            sffile.remove_file()
            return redirect('.')

    def picked_file(self):
        get_response().set_content_type('application/json')
        sffile = StrongboxItem.get(get_request().form.get('val'))
        sffile.file.fp = file(sffile.file.filename)
        if sffile.user_id != get_request().user.id:
            raise errors.TraversalError()
        # XXX: this will copy the file, it would be quite nice if it was
        # possible to just make it a symlink to the sffile
        token = get_session().add_tempfile(sffile.file)
        return json.dumps({'token': token, 'filename': sffile.file.base_filename})

    def pick(self):
        if get_request().form.get('select') == 'true':
            return self.picked_file()
        r = TemplateIO(html=True)
        root_url = get_publisher().get_root_url()
        sffiles = list(StrongboxItem.get_with_indexed_value(
                        str('user_id'), str(get_request().user.id)))
        r += htmltext('<h2>%s</h2>') % _('Pick a file')

        if not sffiles:
            r += htmltext('<p>')
            r += _('You do not have any file in your strongbox at the moment.')
            r += htmltext('</p>')
            r += htmltext('<div class="buttons">')
            r += htmltext('<a href="%smyspace/strongbox/" target="_blank">%s</a>') % (root_url,
                _('Open Strongbox Management'))
            r += htmltext('</div>')
        else:
            r += htmltext('<form id="strongbox-pick">')
            r += htmltext('<ul>')
            for sffile in sffiles:
                r += htmltext('<li><label><input type="radio" name="file" value="%s"/>%s</label>') % (
                                sffile.id, sffile.get_display_name())
                r += htmltext(' [<a href="%smyspace/strongbox/download?id=%s">%s</a>] ') % (
                                root_url, sffile.id, _('view'))
                r += htmltext('</li>')
            r += htmltext('</ul>')

            r += htmltext('<div class="buttons">')
            r += htmltext('<input name="cancel" type="button" value="%s"/>') % _('Cancel')
            r += ' '
            r += htmltext('<input name="pick" type="button" value="%s"/>') % _('Pick')
            r += htmltext('</div>')
            r += htmltext('</form>')
        return r.getvalue()


class JsonDirectory(Directory):
    '''Export of several lists in json, related to the current user or the
       SAMLv2 NameID we'd get in the URL'''

    _q_exports = ['forms']

    user = None

    def _q_traverse(self, path):
        self.user = get_user_from_api_query_string() or get_request().user
        if not self.user:
            raise errors.AccessUnauthorizedError()
        return Directory._q_traverse(self, path)

    def forms(self):
        formdefs = FormDef.select(order_by='name', ignore_errors=True)
        user_forms = []
        for formdef in formdefs:
            user_forms.extend(formdef.data_class().get_with_indexed_value(
                        'user_id', self.user.id))
        user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))

        get_response().set_content_type('application/json')


        forms_output = []
        for form in user_forms:
            visible_status = form.get_visible_status(user=self.user)
            # skip drafts and hidden forms
            if not visible_status:
                continue
            name = form.formdef.name
            id = form.get_display_id()
            status = visible_status.name
            title = _('%(name)s #%(id)s (%(status)s)') % {
                    'name': name,
                    'id': id,
                    'status': status
            }
            url = form.get_url()
            d = { 'title': title, 'url': url }
            d.update(form.get_substitution_variables(minimal=True))
            forms_output.append(d)
        return json.dumps(forms_output, cls=misc.JSONEncoder)


class MyspaceDirectory(wcs.myspace.MyspaceDirectory):
    _q_exports = ['', 'profile', 'new', 'password', 'remove', 'drafts', 'forms',
            'announces', 'strongbox', 'invoices', 'json']

    strongbox = StrongboxDirectory()
    invoices = MyInvoicesDirectory()
    json = JsonDirectory()

    def _q_traverse(self, path):
        get_response().filter['bigdiv'] = 'profile'
        get_response().breadcrumb.append(('myspace/', _('My Space')))

        # Migrate custom text settings
        texts_cfg = get_cfg('texts', {})
        if 'text-aq-top-of-profile' in texts_cfg and (
                        not 'text-top-of-profile' in texts_cfg):
            texts_cfg['text-top-of-profile'] = texts_cfg['text-aq-top-of-profile']
            del texts_cfg['text-aq-top-of-profile']
            get_publisher().write_cfg()

        return Directory._q_traverse(self, path)


    def _q_index(self):
        user = get_request().user
        if not user:
            raise errors.AccessUnauthorizedError()
        template.html_top(_('My Space'))
        r = TemplateIO(html=True)
        if user.anonymous:
            return redirect('new')

        user_formdef = user.get_formdef()

        user_forms = []
        if user:
            formdefs = FormDef.select(order_by='name', ignore_errors=True)
            user_forms = []
            for formdef in formdefs:
                user_forms.extend(formdef.data_class().get_with_indexed_value(
                            'user_id', user.id))
            user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))

        profile_links = []
        if not get_cfg('sp', {}).get('idp-manage-user-attributes', False):
            if user_formdef:
                profile_links.append('<a href="#my-profile">%s</a>' % _('My Profile'))
        if user_forms:
            profile_links.append('<a href="#my-forms">%s</a>' % _('My Forms'))
        if get_cfg('misc', {}).get('aq-strongbox'):
            profile_links.append('<a href="strongbox/">%s</a>' % _('My Strongbox'))
        if is_payment_supported():
            profile_links.append('<a href="invoices/">%s</a>' % _('My Invoices'))

        root_url = get_publisher().get_root_url()
        if user.can_go_in_backoffice():
            profile_links.append('<a href="%sbackoffice/">%s</a>' % (root_url, _('Back office')))
        if user.is_admin:
            profile_links.append('<a href="%sadmin/">%s</a>' % (root_url, _('Admin')))

        if profile_links:
            r += htmltext('<p id="profile-links">')
            r += htmltext(' - '.join(profile_links))
            r += htmltext('</p>')

        if not get_cfg('sp', {}).get('idp-manage-user-attributes', False):
            if user_formdef:
                r += self._my_profile(user_formdef, user)

            r += self._index_buttons(user_formdef)

            try:
                x = PasswordAccount.get_on_index(get_request().user.id, str('user_id'))
            except KeyError:
                pass
            else:
                r += htmltext('<p>')
                r += _('You can delete your account freely from the services portal. '
                       'This action is irreversible; it will destruct your personal '
                       'datas and destruct the access to your request history.')
                r += htmltext(' <strong><a href="remove" rel="popup">%s</a></strong>.') % _('Delete My Account')
                r += htmltext('</p>')

        options = get_cfg('misc', {}).get('announce_themes')
        if options:
            try:
                subscription = AnnounceSubscription.get_on_index(
                        get_request().user.id, str('user_id'))
            except KeyError:
                pass
            else:
                r += htmltext('<p class="command"><a href="announces">%s</a></p>') % _(
                        'Edit my Subscription to Announces')

        if user_forms:
            r += htmltext('<h3 id="my-forms">%s</h3>') % _('My Forms')
            from . import root
            r += root.FormsRootDirectory().user_forms(user_forms)

        return r.getvalue()

    def _my_profile(self, user_formdef, user):
        r = TemplateIO(html=True)
        r += htmltext('<h3 id="my-profile">%s</h3>') % _('My Profile')

        r += TextsDirectory.get_html_text('top-of-profile')

        if user.form_data:
            r += htmltext('<ul>')
            for field in user_formdef.fields:
                if not hasattr(field, str('get_view_value')):
                    continue
                value = user.form_data.get(field.id)
                r += htmltext('<li>')
                r += field.label
                r += ' : '
                if value:
                    r += field.get_view_value(value)
                r += htmltext('</li>')
            r += htmltext('</ul>')
        else:
            r += htmltext('<p>%s</p>') % _('Empty profile')
        return r.getvalue()

    def _index_buttons(self, form_data):
        r = TemplateIO(html=True)
        passwords_cfg = get_cfg('passwords', {})
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
        if get_session().lasso_session_dump:
            ident_method = 'idp'

        if form_data and ident_method != 'idp':
            r += htmltext('<p class="command"><a href="profile" rel="popup">%s</a></p>') % _('Edit My Profile')

        if ident_method == 'password' and passwords_cfg.get('can_change', False):
            r += htmltext('<p class="command"><a href="password" rel="popup">%s</a></p>') % _('Change My Password')

        return r.getvalue()

    def profile(self):
        user = get_request().user
        if not user or user.anonymous:
            raise errors.AccessUnauthorizedError()

        form = Form(enctype = 'multipart/form-data')
        formdef = user.get_formdef()
        formdef.add_fields_to_form(form, form_data = user.form_data)

        form.add_submit('submit', _('Apply Changes'))
        form.add_submit('cancel', _('Cancel'))

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

        if form.is_submitted() and not form.has_errors():
            self.profile_submit(form, formdef)
            return redirect('.')

        template.html_top(_('Edit Profile'))
        return form.render()

    def profile_submit(self, form, formdef):
        user = get_request().user
        data = formdef.get_data(form)

        user.set_attributes_from_formdata(data)
        user.form_data = data

        user.store()

    def password(self):
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
        if ident_method != 'password':
            raise errors.TraversalError()

        user = get_request().user
        if not user or user.anonymous:
            raise errors.AccessUnauthorizedError()

        form = Form(enctype = 'multipart/form-data')
        form.add(PasswordWidget, 'new_password', title = _('New Password'),
                required=True)
        form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'),
                required=True) 

        form.add_submit('submit', _('Change Password'))
        form.add_submit('cancel', _('Cancel'))

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

        if form.is_submitted() and not form.has_errors():
            wcs.qommon.ident.password.check_password(form, 'new_password')
            new_password = form.get_widget('new_password').parse()
            new2_password = form.get_widget('new2_password').parse()
            if new_password != new2_password:
                form.set_error('new2_password', _('Passwords do not match'))

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

        template.html_top(_('Change Password'))
        return form.render()

    def submit_password(self, new_password):
        passwords_cfg = get_cfg('passwords', {})
        account = PasswordAccount.get(get_session().username)
        account.hashing_algo = passwords_cfg.get('hashing_algo')
        account.set_password(new_password)
        account.store()

    def new(self):
        if not get_request().user or not get_request().user.anonymous:
            raise errors.AccessUnauthorizedError()

        form = Form(enctype = 'multipart/form-data')
        formdef = get_publisher().user_class.get_formdef()
        if formdef:
            formdef.add_fields_to_form(form)
        else:
            get_logger().error('missing user formdef (in myspace/new)')

        form.add_submit('submit', _('Register'))

        if form.is_submitted() and not form.has_errors():
            user = get_publisher().user_class()
            data = formdef.get_data(form)
            user.set_attributes_from_formdata(data)
            user.name_identifiers = get_request().user.name_identifiers
            user.lasso_dump = get_request().user.lasso_dump
            user.set_attributes_from_formdata(data)
            user.form_data = data
            user.store()
            get_session().set_user(user.id)
            root_url = get_publisher().get_root_url()
            return redirect('%smyspace' % root_url)

        template.html_top(_('Welcome'))
        return form.render()


    def remove(self):
        user = get_request().user
        if not user or user.anonymous:
            raise errors.AccessUnauthorizedError()

        form = Form(enctype = 'multipart/form-data')
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
                        'Are you really sure you want to remove your account?')))
        form.add_submit('submit', _('Remove my account'))
        form.add_submit('cancel', _('Cancel'))

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

        if form.is_submitted() and not form.has_errors():
            user = get_request().user
            account = PasswordAccount.get_on_index(user.id, str('user_id'))
            get_session_manager().expire_session() 
            account.remove_self()
            return redirect(get_publisher().get_root_url())

        template.html_top(_('Removing Account'))
        return form.render()

    def announces(self):
        options = get_cfg('misc', {}).get('announce_themes')
        if not options:
            raise errors.TraversalError()
        user = get_request().user
        if not user or user.anonymous:
            raise errors.AccessUnauthorizedError()
        subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id'))
        if not subscription:
            raise errors.TraversalError()

        if subscription.enabled_themes is None:
            enabled_themes = options
        else:
            enabled_themes = subscription.enabled_themes

        form = Form(enctype = 'multipart/form-data')
        form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'),
                value=enabled_themes, options=[(x, x, x) for x in options],
                inline=False, required=False)

        form.add_submit('submit', _('Apply Changes'))
        form.add_submit('cancel', _('Cancel'))

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

        if form.is_submitted() and not form.has_errors():
            chosen_themes = form.get_widget('themes').parse()
            if chosen_themes == options:
                chosen_themes = None
            subscription.enabled_themes = chosen_themes
            subscription.store()
            return redirect('.')

        template.html_top()
        get_response().breadcrumb.append(('announces', _('Announce Subscription')))
        return form.render()


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

