# Copyright (c) 2014 Riverbed Technology, Inc.
#
# This software is licensed under the terms and conditions of the MIT License
# accompanying the software ("License").  This software is distributed "AS IS"
# as set forth in the License.


import sys
import time
import datetime
import logging
import optparse
logger = logging.getLogger(__name__)

from django.core.management.base import BaseCommand

from steelscript.common.datautils import Formatter

from steelscript.appfwk.apps.datasource.models import Table, Job
from steelscript.appfwk.apps.datasource.forms import TableFieldForm
from steelscript.appfwk.apps.report.models import Report, Widget

# not pretty, but pandas insists on warning about
# some deprecated behavior we really don't care about
# for this script, so ignore them all
import warnings
warnings.filterwarnings("ignore")


class Command(BaseCommand):
    args = None
    help = 'Run a defined table and return results in nice tabular format'

    def create_parser(self, prog_name, subcommand):
        """ Override super version to include special option grouping. """
        parser = super(Command, self).create_parser(prog_name, subcommand)
        group = optparse.OptionGroup(
            parser, "Run Table Help",
            "Helper commands to display list of tables to run"
        )
        group.add_option('--table-list',
                         action='store_true',
                         dest='table_list',
                         default=False,
                         help='List all available tables by id')
        group.add_option('--table-list-by-report',
                         action='store_true',
                         dest='table_list_by_report',
                         default=False,
                         help='List tables organized by report')
        parser.add_option_group(group)

        group = optparse.OptionGroup(
            parser, "Run Table Criteria",
            "Options to specify criteria for specified run table.\n"
            "When adding time-based criteria, the timezone offset "
            "must be included, otherwise the time will be assumed as UTC.\n"
            "For example:\n"
            '"python manage.py table --table-id 19 -C endtime:9:01-0500"',
        )
        group.add_option(
            '--table-id',
            action='store',
            dest='table_id',
            help='Table ID to execute (use --table-list to find the right ID)'
        )
        group.add_option(
            '--table-name',
            action='store',
            dest='table_name',
            help='Table name to execute (use --table-list to list all tables)'
        )
        group.add_option(
            '-C', '--criteria',
            action='append',
            type='str',
            dest='criteria',
            default=None,
            help='Specify criteria as <key>:<value>, repeat as necessary'
        )
        group.add_option('--criteria-list',
                         action='store_true',
                         dest='criteria_list',
                         default=None,
                         help='List criteria for this table')

        parser.add_option_group(group)

        group = optparse.OptionGroup(parser, "Run Table Output Options",
                                     "Specify how data should be displayed")

        group.add_option('-o', '--output-file',
                         dest='output_file',
                         default=None,
                         help='Output data to a file')
        group.add_option('--csv',
                         action='store_true',
                         dest='as_csv',
                         default=False,
                         help='Output data in CSV format instead of tabular')
        group.add_option('--columns',
                         action='store_true',
                         dest='only_columns',
                         default=False,
                         help='Output only columns ignoring data')
        parser.add_option_group(group)
        return parser

    def console(self, msg, ending=None):
        """ Print text to console except if we are writing CSV file. """
        if not self.options['as_csv']:
            self.stdout.write(msg, ending=ending)
            self.stdout.flush()

    def get_form(self, table, data=None):
        # First see if there's a single report associated with this table,
        # and if so, use it to get the field set
        widgets = Widget.objects.filter(tables__in=[table])
        sections = set()
        for w in widgets:
            sections.add(w.section)

        if len(sections) == 1:
            all_fields = widgets[0].collect_fields()
        else:
            all_fields = {}
            for f in table.fields.all():
                all_fields[f.keyword] = f

        return TableFieldForm(all_fields, use_widgets=False, data=data)

    def handle(self, *args, **options):
        """ Main command handler. """
        self.options = options

        if options['table_list']:
            # print out the id's instead of processing anything
            output = []
            for t in Table.objects.all():
                output.append([t.id, t.namespace, t.datasource, t.name, t])
            Formatter.print_table(output, ['ID', 'Namespace', 'Datasource',
                                           'Name', 'Table'])
        elif options['table_list_by_report']:
            # or print them out organized by report/widget/table
            output = []
            reports = Report.objects.all()
            for report in reports:
                for table in report.tables():
                    for widget in table.widget_set.all():
                        line = [table.id, report.title, widget.title, table]
                        output.append(line)
            Formatter.print_table(output, ['ID', 'Report', 'Widget', 'Table'])
        elif options['criteria_list']:
            if 'table_id' in options and options['table_id'] is not None:
                table = Table.objects.get(id=options['table_id'])
            elif 'table_name' in options:
                table = Table.objects.get(name=options['table_name'])
            else:
                raise ValueError("Must specify either --table-id or "
                                 "--table-name to run a table")

            form = self.get_form(table)

            # Only show criteria options that were included in report
            # and given a label, other ones are for internal table use.
            # criteria like ignore_cache can still be passed in, they
            # just won't be shown in this list
            output = [(k, v.label)
                      for k, v in form.fields.iteritems() if v.label]
            Formatter.print_table(output, ['Keyword', 'Label'])
        else:
            if 'table_id' in options and options['table_id'] is not None:
                table = Table.objects.get(id=options['table_id'])
            elif 'table_name' in options:
                table = Table.objects.get(name=options['table_name'])
            else:
                raise ValueError("Must specify either --table-id or "
                                 "--table-name to run a table")

            # Django gives us a nice error if we can't find the table
            self.console('Table %s found.' % table)

            # Parse criteria options
            criteria_options = {}
            if 'criteria' in options and options['criteria'] is not None:
                for s in options['criteria']:
                    (k, v) = s.split(':', 1)
                    criteria_options[k] = v

            form = self.get_form(table, data=criteria_options)

            if not form.is_valid(check_unknown=True):
                self.console('Invalid criteria:')
                logger.error('Invalid criteria: %s' %
                             ','.join('%s:%s' % (k, v)
                                      for k, v in form.errors.iteritems()))
                for k, v in form.errors.iteritems():
                    self.console('  %s: %s' % (k, ','.join(v)))

                sys.exit(1)

            criteria = form.criteria()

            columns = [c.name for c in table.get_columns()]

            if options['only_columns']:
                print columns
                return

            job = Job.create(table=table, criteria=criteria,
                             update_progress=False)
            job.save()

            self.console('Job created: %s' % job)
            self.console('Criteria: %s' % criteria.print_details())

            start_time = datetime.datetime.now()
            job.start()
            self.console('Job running . . ', ending='')

            # wait for results
            while not job.done():
                # self.console('. ', ending='')
                # self.stdout.flush()
                time.sleep(1)

            end_time = datetime.datetime.now()
            delta = end_time - start_time
            seconds = float(delta.microseconds +
                            (delta.seconds + delta.days*24*3600)*10**6)/10**6

            self.console('Done!! (elapsed time: %.2f seconds)' % seconds)
            self.console('')

            # Need to refresh the column list in case the job changed them
            # (ephemeral cols)
            columns = [c.name for c in table.get_columns()]

            if job.status == job.COMPLETE:
                if options['as_csv']:
                    if options['output_file']:
                        with open(options['output_file'], 'w') as f:
                            for line in Formatter.get_csv(job.values(),
                                                          columns):
                                f.write(line)
                                f.write('\n')
                    else:
                        Formatter.print_csv(job.values(), columns)
                else:
                    Formatter.print_table(job.values(), columns)
            else:
                self.console("Job completed with an error:")
                self.console(job.message)
                sys.exit(1)
