# 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 csv
import datetime as dt
import json
import io
from numbers import Number
try:
    from http import HTTPStatus
except ImportError:
    from http import client as HTTPStatus

from werkzeug.exceptions import abort
from werkzeug.utils import redirect
from werkzeug.wrappers import Response

from trytond.i18n import gettext
from trytond.config import config
from trytond.wsgi import app
from trytond.protocols.jsonrpc import JSONDecoder
from trytond.protocols.wrappers import with_pool, with_transaction
from trytond.tools import slugify
from trytond.transaction import Transaction

SOURCE = config.get(
    'html', 'src', default='https://cloud.tinymce.com/stable/tinymce.min.js')
AVATAR_TIMEOUT = config.getint(
    'web', 'avatar_timeout', default=7 * 24 * 60 * 60)


def get_token(record):
    return str((record.write_date or record.create_date).timestamp())


def get_config(names, section='html', default=None):
    names = names[:]
    while names:
        value = config.get(section, '-'.join(names))
        if value is not None:
            return value
        names = names[:-1]
    return default


@app.route('/<database_name>/ir/html/<model>/<int:record>/<field>',
    methods={'GET', 'POST'})
@app.auth_required
@with_pool
@with_transaction(
    user='request', context=dict(_check_access=True, fuzzy_translation=True))
def html_editor(request, pool, model, record, field):
    Field = pool.get('ir.model.field')
    field, = Field.search([
            ('name', '=', field),
            ('model.model', '=', model),
            ])

    transaction = Transaction()
    language = request.args.get('language', transaction.language)
    with transaction.set_context(language=language):
        Model = pool.get(model)
        record = Model(record)

        status = HTTPStatus.OK
        error = ''
        if request.method == 'POST':
            setattr(record, field.name, request.form['text'])
            if request.form['_csrf_token'] == get_token(record):
                record.save()
                return redirect(request.url)
            else:
                status = HTTPStatus.BAD_REQUEST
                error = gettext('ir.msg_html_editor_save_fail')

        csrf_token = get_token(record)
        text = getattr(record, field.name) or ''
        if isinstance(text, bytes):
            try:
                text = text.decode('utf-8')
            except UnicodeDecodeError as e:
                error = str(e).replace("'", "\\'")
                text = ''
        elif not isinstance(text, str):
            abort(HTTPStatus.BAD_REQUEST)
        title = '%(model)s "%(name)s" %(field)s - %(title)s' % {
            'model': field.model.name,
            'name': record.rec_name,
            'field': field.field_description,
            'title': request.args.get('title', "Tryton"),
            }

        return Response(TEMPLATE % {
                'source': SOURCE,
                'plugins': get_config(
                    ['plugins', model, field.name], default=''),
                'css': get_config(
                    ['css', model, field.name], default='[]'),
                'class': get_config(
                    ['class', model, field.name], default="''"),
                'language': transaction.language,
                'title': title,
                'text': text,
                'csrf_token': csrf_token,
                'error': error,
                }, status, content_type='text/html')


TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <script src="%(source)s"></script>
    <script>
    tinymce.init({
        selector: '#text',
        language: '%(language)s',
        plugins: 'fullscreen autosave code %(plugins)s',
        removed_menuitems: 'newdocument',
        toolbar: 'save | undo redo | styleselect | bold italic | ' +
            'alignleft aligncenter alignright alignjustify | ' +
            'bullist numlist outdent indent | link image | close',
        extended_valid_elements:
            'py:if[test],' +
            'py:choose[test],py:when[test],py:otherwise,' +
            'py:for[each],' +
            'py:def[function],' +
            'py:match[path],' +
            'py:with[vars],' +
            'py:replace[value]',
        custom_elements:
            'py:if,' +
            'py:choose,py:when,py:otherwise,' +
            'py:for,' +
            'py:def,' +
            'py:match,' +
            'py:with,' +
            'py:replace',
        content_css: %(css)s,
        body_class: %(class)s,
        setup: function(editor) {
            editor.addMenuItem('save', {
                text: 'Save',
                icon: 'save',
                context: 'file',
                cmd: 'save',
            });
            editor.addButton('save', {
                title: 'Save',
                icon: 'save',
                cmd: 'save',
            });
            editor.addShortcut('ctrl+s', 'save', 'save');
            editor.addCommand('save', function() {
                document.form.submit();
            });
            editor.addButton('close', {
                title: 'Close',
                icon: 'remove',
                onclick: function() {
                    window.location =
                        window.location.protocol + '//_@' +
                        window.location.host +
                        window.location.pathname;
                    window.close();
                },
            });
        },
        init_instance_callback: function(editor) {
            editor.execCommand('mceFullScreen');
            var error = '%(error)s';
            if (error) {
                editor.notificationManager.open({
                    text: error,
                    type: 'error',
                });
            }
        },
    });
    </script>
    <title>%(title)s</title>
</head>
<body>
    <form name="form" method="post" style="display: block; text-align: center">
        <textarea id="text" name="text">%(text)s</textarea>
        <input name="_csrf_token" type="hidden" value="%(csrf_token)s">
    </form>
</body>
</html>'''


@app.route('/<database_name>/data/<model>', methods={'GET'})
@app.auth_required
@with_pool
@with_transaction(user='request', context=dict(_check_access=True))
def data(request, pool, model):
    User = pool.get('res.user')
    Lang = pool.get('ir.lang')
    try:
        Model = pool.get(model)
    except KeyError:
        abort(HTTPStatus.NOT_FOUND)
    transaction = Transaction()
    context = User(transaction.user).get_preferences(context_only=True)
    language = request.args.get('l')
    if language:
        context['language'] = language
    try:
        domain = json.loads(
            request.args.get('d', '[]'), object_hook=JSONDecoder())
    except json.JSONDecodeError:
        abort(HTTPStatus.BAD_REQUEST)
    try:
        ctx = json.loads(
            request.args.get('c', '{}'), object_hook=JSONDecoder())
    except json.JSONDecoder:
        abort(HTTPStatus.BAD_REQUEST)
    for key in list(ctx.keys()):
        if key.startswith('_') and key != '_datetime':
            del ctx[key]
    context.update(ctx)
    limit = None
    offset = 0
    if 's' in request.args:
        try:
            limit = int(request.args.get('s'))
            if 'p' in request.args:
                offset = int(request.args.get('p')) * limit
        except ValueError:
            abort(HTTPStatus.BAD_REQUEST)
    if 'o' in request.args:
        order = [(o.split(',', 1) + [''])[:2]
            for o in request.args.getlist('o')]
    else:
        order = None
    fields_names = request.args.getlist('f')
    encoding = request.args.get('enc', 'UTF-8')
    delimiter = request.args.get('dl', ',')
    quotechar = request.args.get('qc', '"')
    try:
        header = bool(int(request.args.get('h', True)))
        locale_format = bool(int(request.args.get('loc', False)))
    except ValueError:
        abort(HTTPStatus.BAD_REQUEST)

    with transaction.set_context(**context):
        lang = Lang.get(transaction.language)

        def format_(row):
            for i, value in enumerate(row):
                if locale_format:
                    if isinstance(value, Number):
                        value = lang.format('%.12g', value)
                    elif isinstance(value, (dt.date, dt.datetime)):
                        value = lang.strftime(value)
                elif isinstance(value, bool):
                    value = int(value)
                row[i] = value
            return row

        try:
            if domain and isinstance(domain[0], (int, float)):
                rows = Model.export_data(domain, fields_names)
            else:
                rows = Model.export_data_domain(
                    domain, fields_names,
                    limit=limit, offset=offset, order=order)
        except (ValueError, KeyError):
            abort(HTTPStatus.BAD_REQUEST)
        data = io.StringIO(newline='')
        writer = csv.writer(data, delimiter=delimiter, quotechar=quotechar)
        if header:
            writer.writerow(fields_names)
        for row in rows:
            writer.writerow(format_(row))
        data = data.getvalue().encode(encoding)
        filename = slugify(Model.__names__()['model']) + '.csv'
        filename = filename.encode('latin-1', 'ignore')
        response = Response(data, mimetype='text/csv; charset=' + encoding)
        response.headers.add(
            'Content-Disposition', 'attachment', filename=filename)
        response.headers.add('Content-Length', len(data))
        return response


@app.route('/avatar/<base64:database_name>/<uuid>', methods={'GET'})
@with_pool
@with_transaction()
def avatar(request, pool, uuid):
    Avatar = pool.get('ir.avatar')

    try:
        avatar, = Avatar.search([
                ('uuid', '=', uuid),
                ])
    except ValueError:
        abort(HTTPStatus.NOT_FOUND)
    try:
        size = int(request.args.get('s', 64))
    except ValueError:
        abort(HTTPStatus.BAD_REQUEST)
    response = Response(avatar.get(size), mimetype='image/jpeg')
    response.headers['Cache-Control'] = (
        'max-age=%s, public' % AVATAR_TIMEOUT)
    response.add_etag()
    return response
