# w.c.s. - web application for online forms
# Copyright (C) 2005-2018  Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import os
import sys
import psycopg2
import traceback

from django.utils.encoding import force_bytes
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.utils.six import StringIO

from wcs.qommon.publisher import get_publisher_class

from wcs import sql
from wcs.formdef import FormDef
from wcs.qommon.misc import localstrftime
from wcs.qommon.storage import atomic_write
from wcs.users import User


class Command(BaseCommand):

    help = 'Setup postgresql connection parameters and migrate existing objects.'

    def add_arguments(self, parser):
        parser.add_argument('-d', '--domain', required=True)
        parser.add_argument('--database', required=True)
        parser.add_argument('--host')
        parser.add_argument('--port', type=int)
        parser.add_argument('--user')
        parser.add_argument('--password')

    def handle(self, **options):
        self.publisher = self.get_publisher(options['domain'])
        if self.publisher.is_using_postgresql():
            raise CommandError('tenant already using postgresql')

        self.setup_connection(**options)
        sql.get_connection(new=True)
        self.store_users()
        self.store_forms()
        self.publisher.write_cfg()
        self.enable_connection()
        self.publisher.cleanup()

    def get_publisher(self, domain):
        publisher_class = get_publisher_class()
        if domain not in publisher_class.get_tenants():
            raise CommandError('unknown tenant')
        publisher = publisher_class.create_publisher()

        publisher.app_dir = os.path.join(publisher.app_dir, domain)
        publisher.set_config()
        return publisher

    def setup_connection(self, **kwargs):
        options = {}
        for k in ['host', 'port', 'database', 'user', 'password']:
            if k in kwargs:
                options[k] = kwargs.get(k)
        self.publisher.cfg['postgresql'] = options

    def enable_connection(self):
        if not self.publisher.site_options.has_option('options', 'postgresql'):
            if not self.publisher.site_options.has_section('options'):
                self.publisher.site_options.add_section('options')
            self.publisher.site_options.set('options', 'postgresql', 'true')
            options_file = os.path.join(self.publisher.app_dir, 'site-options.cfg')
            stringio = StringIO()
            self.publisher.site_options.write(stringio)
            atomic_write(options_file, force_bytes(stringio.getvalue()))

    def store_users(self):
        errors = []
        print('converting users')
        sql.do_user_table()
        count = User.count()
        for i, user_id in enumerate(User.keys()):
            user = User.get(user_id)
            user.__class__ = sql.SqlUser
            try:
                user.store()
            except AssertionError:
                errors.append((user, traceback.format_exc()))
            self.update_progress(100*i/count)
        sql.SqlUser.fix_sequences()

        if errors:
            error_log = open('error_user.log', 'w')
            for user, trace in errors:
                error_log.write('user_id %s\n' % user.id)
                error_log.write(trace)
                error_log.write('-'*80)
                error_log.write('\n\n')
            error_log.close()
            print('There were some errors, see error_user.log for details.')

    def store_forms(self):
        errors = []
        for formdef in FormDef.select():
            print('converting %s' % formdef.name)
            sql.do_formdef_tables(formdef, rebuild_views=True,
                                  rebuild_global_views=True)
            data_class = formdef.data_class(mode='files')
            count = data_class.count()

            # load all objects a first time, to allow the migrate() code to be
            # run and the eventual changes properly saved.
            for id in data_class.keys():
                formdata = data_class.get(id)
            delattr(sys.modules['formdef'], formdef.url_name.title())

            # once this is done, reload and store everything in postgresql
            sql_data_class = formdef.data_class(mode='sql')
            for i, id in enumerate(data_class.keys()):
                formdata = data_class.get(id)
                formdata._formdef = formdef
                formdata._evolution = formdata.evolution
                formdata.__class__ = sql_data_class
                try:
                    formdata.store()
                except (AssertionError, psycopg2.DataError):
                    errors.append((formdata, traceback.format_exc()))
                self.update_progress(100*i/count)
            sql_data_class.fix_sequences()

        sql.do_tracking_code_table()
        sql.do_session_table()
        sql.do_meta_table()

        if errors:
            error_log = open('error_formdata.log', 'w')
            for formdata, trace in errors:
                error_log.write('%s %s - %s\n' % (formdata.formdef,
                                                  formdata.id,
                                                  localstrftime(formdata.receipt_time)))
                error_log.write(trace)
                error_log.write('-'*80)
                error_log.write('\n\n')
            error_log.close()
            print('There were some errors, see error_formdata.log.')

    def update_progress(self, progress, num_columns=120):
        sys.stdout.write('[%s] %s%%\r' % (
            ('#'*int((num_columns-10)*progress/100)).ljust(num_columns-15), progress))
