# -*- coding: utf-8 -*-

import collections
import datetime
import itertools
import logging
import operator
import colander

from colander import SchemaNode

from deform.widget import SelectWidget

import sqlalchemy as sa
from sqlalchemy.orm import lazyload
from repoze.workflow import get_workflow
from webhelpers.html.builder import HTML, literal

from deform import ValidationFailure
from pyramid.renderers import render
from pyramid.view import view_config

from por.dashboard.lib.helpers import ticket_url, unicodelower, timeentry_url, ReversedOrder, total_seconds
from por.dashboard.lib.widgets import SearchButton, PorInlineForm
from por.dashboard.reports import fields
from por.dashboard.reports.queries import qry_active_projects, te_filter_by_customer_requests, NullCustomerRequest, filter_users_with_timeentries
from por.dashboard.reports.validators import validate_period
from por.dashboard.reports.favourites import render_saved_query_form
from por.dashboard.events import AfterEntryCreatedEvent

from por.models import DBSession, Project, TimeEntry, User, CustomerRequest
from por.models.tickets import ticket_store
from por.models.tp import timedelta_as_human_str

log = logging.getLogger(__name__)




class CustomerReport(object):
    def __init__(self, context, request):
        self.context = context
        self.request = request


    class CustomSchema(colander.MappingSchema):
        customer_id = fields.customer_id.clone()
        customer_id.missing = colander.null

        project_id = fields.project_id.clone()
        customer_requests = fields.customer_requests.clone()
        date_from = fields.date_from.clone()
        date_to = fields.date_to.clone()
        users = fields.users.clone()
        workflow_states = fields.workflow_states.clone()

        detail_level = SchemaNode(colander.String(),
                                  validator=colander.OneOf(['project', 'request', 'ticket', 'timeentry', 'date']),
                                  widget=SelectWidget(values=[
                                          ('project', 'Project'),
                                          ('request', 'Request'),
                                          ('ticket', 'Ticket'),
                                          ('timeentry', 'Time Entry'),
                                          ('date', 'Date'),
                                      ]),
                                  default='timeentry',
                                  missing=colander.null,
                                  title=u'Detail')

    def format_web(self, value):
        if isinstance(value, datetime.timedelta):
            return timedelta_as_human_str(value)
        return value


    def format_xls(self, value):
        if isinstance(value, datetime.timedelta):
            # Render durations as float in order to sum them
            return total_seconds(value) / 60.0 / 60.0
        if isinstance(value, literal):
            # sanitize HTML
            return value.striptags()
        return value


    def search(self, customer_id, project_id, date_from, date_to,
               users, customer_requests, workflow_states, detail_level, render_links=True):

        # also search archived projects, if none are specified
        qry = DBSession.query(TimeEntry).join(TimeEntry.project).join(Project.customer).outerjoin(TimeEntry.author)
        qry = qry.options(lazyload(TimeEntry.project, TimeEntry.author, Project.customer))

        if customer_id is not colander.null:
            qry = qry.filter(Project.customer_id == customer_id)

        if project_id is not colander.null:
            qry = qry.filter(TimeEntry.project_id == project_id)

        if date_from is not colander.null:
            qry = qry.filter(TimeEntry.date>=date_from)

        if date_to is not colander.null:
            qry = qry.filter(TimeEntry.date<=date_to)

        if users:
            qry = qry.filter(TimeEntry.author_id.in_(users))

        if workflow_states:
            qry = qry.filter(TimeEntry.workflow_state.in_(workflow_states))

        qry = qry.filter(te_filter_by_customer_requests(customer_requests, request=self.request))

        qry = qry.order_by(sa.desc(TimeEntry.date), sa.desc(TimeEntry.start), sa.desc(TimeEntry.creation_date))

        time_entries = self.request.filter_viewables(qry)

        proj_tickets = collections.defaultdict(set)
        for te in time_entries:
            if te.ticket is None:
                continue
            proj_tickets[te.project_id].add(te.ticket)

        # projectsmap = {
        #    'project_id': {
        #         'ticket_id': 'customer_request_id',
        #         ...
        #    },
        #    ...
        # }

        projectsmap = {}
        for project_id, ticket_ids in proj_tickets.items():
            project = DBSession.query(Project).get(project_id)
            projectsmap[project_id] = dict(ticket_store.get_requests_from_tickets(
                                            project, tuple(ticket_ids), request=self.request))

        tkts = {}
        for project_id, ticket_ids in proj_tickets.items():
            project = DBSession.query(Project).get(project_id)
            for tkt in ticket_store.get_tickets_for_project(project, request=self.request):
                tkts[(project_id, tkt['id'])] = tkt

        rows = []

        cr_get = DBSession.query(CustomerRequest).get

        for te in time_entries:
            if te.ticket is None:
                rendered_request = NullCustomerRequest().name
                ticket_type = '-'
                ticket_summary = '-'
            else:
                cr_id = projectsmap[te.project_id].get(te.ticket)
                if not cr_id:
                    continue

                customer_request = cr_get(cr_id)
                if customer_request:
                    if render_links:
                        rendered_request = HTML.A(customer_request.name.strip(),
                                                  href="%s/admin/CustomerRequest/%s" % (
                                                      self.request.application_url,
                                                      customer_request.id
                                                      )
                                                  )
                    else:
                        rendered_request = customer_request.name.strip()
                else:
                    rendered_request = NullCustomerRequest().name


                tkt = tkts[(te.project_id, te.ticket)]

                ticket_type = tkt['type']
                ticket_summary = tkt['summary']
                if render_links:
                    ticket_summary = HTML.A(ticket_summary,
                                            href=ticket_url(request=self.request,
                                                            project=te.project,
                                                            ticket_id=te.ticket))

                description = HTML.A(te.description,
                                     href=timeentry_url(request=self.request,
                                                        time_entry=te))


            entry = {
                        'customer': te.project.customer.name.strip(),
                        'project': te.project.name.strip(),
                        'request': rendered_request,
                        'ticket_summary': ticket_summary,
                        'user': te.author.fullname.strip(),
                        'date': te.date.strftime('%Y-%m-%d'),
                        'description': description,
                        'location': te.location,
                        'ticket_type': ticket_type,
                        'sensitive': ['no', 'yes'][tkt['sensitive']],
                        'hours': te.hours,
                    }

            event = AfterEntryCreatedEvent(entry, te)
            self.request.registry.notify(event)

            rows.append(entry)

        columns = [
                    ('customer', u'Cliente'),
                    ('project', u'Progetto'),
                    ('request', u'Request'),
                    ('ticket_summary', u'Ticket'),
                    ('ticket_type', u'Tipologia'),
                    ('sensitive', u'Sensitive'),
                    ('user', u'Persona'),
                    ('date', u'Data'),
                    ('description', u'Descrizione attività'),
                    ('location', u'Sede'),
                    ('hours', u'Ore'),
                ]

        group_by = {
                    'project': ['customer', 'project', 'user'],
                    'request': ['customer', 'project', 'request', 'user'],
                    'ticket': ['customer', 'project', 'request', 'ticket_summary', 'ticket_type', 'sensitive', 'user'],
                    'timeentry': None,
                    'date': ['user', 'date', 'location'],
                }[detail_level]

        if group_by:
            rows, columns = self.group(rows, columns, group_by)

        return {
                'rows': rows,
                'columns': columns,
                }


    def group(self, rows, columns, group_by):
        missing = set(group_by) - set(c[0] for c in columns)
        if missing:
            raise ValueError(u"Could not find column: %s" % ', '.join(missing))

        hours = collections.defaultdict(datetime.timedelta)

        for key, group in itertools.groupby(rows,
                                            operator.itemgetter(*group_by)):
            for row in group:
                hours[key] += row['hours']

        rows = [
                dict(zip(group_by, key), hours=hours[key])
                for key in hours
                ]

        # order by the grouped columns again
        rows.sort(
                key = lambda row: [
                    # date is sorted descedent
                    (ReversedOrder(row[k]) if k=='date' else row[k])
                    for k in group_by
                    ]
                )

        return rows, [c for c in columns if c[0] in (group_by + ['hours'])]

    @view_config(name='custom_json', route_name='reports', renderer='json', permission='reports_custom')
    def custom_json(self):
        schema = self.CustomSchema(validator=validate_period).clone()
        form = PorInlineForm(schema)
        controls = self.request.GET.items()
        appstruct = form.validate(controls)
        detail = self.search(render_links=False, **appstruct)
        result = []
        for row in detail['rows']:
            row['hours'] = timedelta_as_human_str(row['hours'])
            del row['description']
            result.append(row)
        return result

    @view_config(name='custom_xls', route_name='reports', renderer='xls_report', permission='reports_custom')
    def custom_xls(self):
        schema = self.CustomSchema(validator=validate_period).clone()
        form = PorInlineForm(schema)
        controls = self.request.GET.items()
        appstruct = form.validate(controls)

        detail = self.search(render_links=False, **appstruct)

        columns = detail['columns']

        rows = [
                [
                    self.format_xls(row[col_key])
                    for col_key, col_title in columns
                    ] + [timedelta_as_human_str(row['hours'])]
                for row in detail['rows']
                ]

        return {
                'header': [col_title for col_key, col_title in columns] + ['Ore'],
                'rows': rows,
                }


    @view_config(name='report_custom', route_name='reports', renderer='skin', permission='reports_custom')
    def __call__(self):
        schema = self.CustomSchema(validator=validate_period).clone()
        projects = self.request.filter_viewables(qry_active_projects())

        # select customers that have some active project
        customers = self.request.filter_viewables(sorted(set(p.customer for p in projects), key=unicodelower))

        users = DBSession.query(User).order_by(User.fullname)
        users = filter_users_with_timeentries(users)
        customer_requests = self.request.filter_viewables(DBSession.query(CustomerRequest).order_by(CustomerRequest.name))

        form = PorInlineForm(schema,
                             formid='report-customer',
                             method='GET',
                             buttons=[
                                 SearchButton(title=u'Search'),
                             ])

        workflow = get_workflow(TimeEntry(), 'TimeEntry')

        all_wf_states = [
                            (state, workflow._state_data[state]['title'] or state)
                            for state in workflow._state_order
                        ]

        form['workflow_states'].widget.values = all_wf_states
        # XXX the following validator is broken
        form['workflow_states'].validator = colander.OneOf([str(ws[0]) for ws in all_wf_states])

        form['customer_id'].widget.values = [('', '')] + [(str(c.id), c.name) for c in customers]
        # don't validate as it might be an archived customer
        form['project_id'].widget.values = [('', '')] + [(str(p.id), p.name) for p in projects]
        # don't validate as it might be an archived project

        form['users'].widget.values = [(str(u.id), u.fullname) for u in users]
        # XXX the following validator is broken
        form['users'].validator = colander.OneOf([str(u.id) for u in users])

        form['customer_requests'].widget.values = [(str(c.id), c.name) for c in customer_requests]
        # XXX the following validator is broken
        form['customer_requests'].validator = colander.OneOf([str(c.id) for c in customer_requests])

        controls = self.request.GET.items()

        if not controls:
            # the form is empty
            return {
                    'form': form.render(),
                    'saved_query_form': render_saved_query_form(self.request),
		    'qs':'',
                    'result_table': None
                    }

        try:
            appstruct = form.validate(controls)
        except ValidationFailure as e:
            return {
                    'form': e.render(),
                    'saved_query_form': render_saved_query_form(self.request),
		    'qs':'',
                    'result_table': None
                    }

        detail = self.search(render_links=True, **appstruct)

        result_table = None

        if detail['rows']:
            base_link = self.request.path_url.rsplit('/', 1)[0]
            xls_link = ''.join([base_link, '/', 'custom_xls', '?', self.request.query_string])
            json_link = ''.join([base_link, '/', 'custom_json', '?', self.request.query_string])
            delta0 = datetime.timedelta()
            delta_tot = sum((row['hours'] for row in detail['rows']), delta0)
            human_tot = timedelta_as_human_str(delta_tot)

            result_table = render('por.dashboard:reports/templates/custom_results.pt',
                                  {
                                      'rows': detail['rows'],
                                      'columns': detail['columns'],
                                      'xls_link': xls_link,
                                      'json_link': json_link,
                                      'format_web': self.format_web,
                                      'human_tot': human_tot,
                                  },
                                  request=self.request)

        return {
                'form': form.render(appstruct=appstruct),
                'saved_query_form': render_saved_query_form(self.request),
                'qs': self.request.query_string,
                'result_table': result_table,
                }


