# -*- coding: UTF-8 -*-
#
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.

import re

from genshi.builder import tag

from trac.core import TracError
from trac.ticket.model import Ticket
from trac.ticket.web_ui import TicketModule
from trac.ticket.report import ReportModule
from trac.attachment import AttachmentModule
from trac.ticket.api import TicketSystem
from trac.resource import Resource, get_resource_shortname, ResourceNotFound
from trac.search import search_to_sql, shorten_result
from trac.util.datefmt import from_utimestamp

from multiproduct.api import MultiProductSystem, PRODUCT_SYNTAX_DELIMITER_RE
from multiproduct.env import ProductEnvironment
from multiproduct.model import Product
from multiproduct.util import IDENTIFIER
from multiproduct.util.translation import _, tag_
from multiproduct.web_ui import ProductModule


class ProductTicketModule(TicketModule):
    """Product Overrides for the TicketModule"""

    # IRequestHandler methods
    #def match_request(self, req):
    # override not yet required

    def process_request(self, req):
        """Override for TicketModule process_request"""
        ticketid = req.args.get('id')
        productid = req.args.get('productid', '')

        if not ticketid:
            # if /newticket is executed in global scope (from QCT), redirect
            # the request to /products/<first_product_in_DB>/newticket
            if not productid and \
                    not isinstance(self.env, ProductEnvironment):
                default_product = self.env.config.get('ticket',
                                                      'default_product')
                products = Product.select(self.env, {'fields': ['prefix']})
                prefixes = [prod.prefix for prod in products]
                if not default_product or default_product not in prefixes:
                    default_product = products[0].prefix
                req.redirect(req.href.products(default_product, 'newticket'))

            return self._process_newticket_request(req)

        if req.path_info in ('/newticket', '/products'):
            raise TracError(_("id can't be set for a new ticket request."))

        if isinstance(self.env, ProductEnvironment):
            ticket = Ticket(self.env, ticketid)
            if productid and ticket['product'] != productid:
                msg = "Ticket %(id)s in product '%(prod)s' does not exist."
                raise ResourceNotFound(_(msg, id=ticketid, prod=productid),
                                       _("Invalid ticket number"))
            return self._process_ticket_request(req)

        # executed in global scope -> assume ticketid=UID, redirect to product
        with self.env.db_direct_query as db:
            rows = db("""SELECT id,product FROM ticket WHERE uid=%s""",
                      (ticketid,))
            if not rows:
                msg = "Ticket with uid %(uid)s does not exist."
                raise ResourceNotFound(_(msg, uid=ticketid),
                                       _("Invalid ticket number"))
            tid, prefix = rows[0]
            req.redirect(req.href.products(prefix, 'ticket', tid))

    # INavigationContributor methods

    #def get_active_navigation_item(self, req):
    # override not yet required

    def get_navigation_items(self, req):
        """Overriding TicketModules New Ticket nav item"""
        return

    # ISearchSource methods
    #def get_search_filters(self, req):
    # override not yet required

    def get_search_results(self, req, terms, filters):
        """Overriding search results for Tickets"""
        if not 'ticket' in filters:
            return
        ticket_realm = Resource('ticket')
        with self.env.db_query as db:
            sql, args = search_to_sql(db, ['summary', 'keywords',
                                           'description', 'reporter', 'cc',
                                           db.cast('id', 'text')], terms)
            sql2, args2 = search_to_sql(db, ['newvalue'], terms)
            sql3, args3 = search_to_sql(db, ['value'], terms)
            ticketsystem = TicketSystem(self.env)
            if req.args.get('product'):
                productsql = "product='%s' AND" % req.args.get('product')
            else:
                productsql = ""

            for summary, desc, author, type, tid, ts, status, resolution in \
                    db("""SELECT summary, description, reporter, type, id,
                                 time, status, resolution
                          FROM ticket
                          WHERE (%s id IN (
                              SELECT id FROM ticket WHERE %s
                            UNION
                              SELECT ticket FROM ticket_change
                              WHERE field='comment' AND %s
                            UNION
                              SELECT ticket FROM ticket_custom WHERE %s
                          ))
                          """ % (productsql, sql, sql2, sql3),
                          args + args2 + args3):
                t = ticket_realm(id=tid)
                if 'TICKET_VIEW' in req.perm(t):
                    yield (req.href.ticket(tid),
                           tag_("%(title)s: %(message)s",
                                title=tag.span(
                                    get_resource_shortname(self.env, t),
                                    class_=status),
                                message=ticketsystem.format_summary(
                                    summary, status, resolution, type)),
                           from_utimestamp(ts), author,
                           shorten_result(desc, terms))

        # Attachments
        for result in AttachmentModule(self.env) \
                      .get_search_results(req, ticket_realm, terms):
            yield result


class ProductReportModule(ReportModule):
    """Multiproduct replacement for ReportModule"""

    # INavigationContributor methods
    #def get_active_navigation_item(self, req):
    # not yet required

    def get_navigation_items(self, req):
        if 'REPORT_VIEW' in req.perm:
            href = ProductModule.get_product_path(self.env, req, 'report')
            yield ('mainnav', 'tickets', tag.a(_('View Tickets'), href=href))

    # IWikiSyntaxProvider methods
    #def get_link_resolvers(self):
    # not yet required

    def get_wiki_syntax(self):
        # FIXME: yield from
        for s in super(ProductReportModule, self).get_wiki_syntax():
            yield s
        # Previously unmatched prefix
        yield (r"!?\{(?P<prp>%s(?:\s+|(?:%s)))[0-9]+\}"
               % (IDENTIFIER, PRODUCT_SYNTAX_DELIMITER_RE),
               lambda x, y, z: self._format_link(x, 'report', y[1:-1], y, z))
        # Absolute product report syntax
        yield (r"!?\{(?P<prns>global:|product:%s(?:\s+|:))"
               r"(?P<prid>[0-9]+)\}" % (IDENTIFIER,),
               lambda x, y, z: (self._format_mplink(x, 'report', y[1:-1], y, z)))

    def _format_link(self, formatter, ns, target, label, fullmatch=None):
        intertrac = \
            formatter.shorthand_intertrac_helper(ns, target, label, fullmatch)
        if intertrac:
            return intertrac

        # second chance to match InterTrac prefix as product prefix
        it_report = fullmatch.group('it_' + ns) or fullmatch.group('prp')
        if it_report:
            return self._format_mplink(formatter, ns, target, label, fullmatch)

        report, args, fragment = formatter.split_link(target)
        return tag.a(label, href=formatter.href.report(report) + args,
                     class_='report')

    def _format_mplink(self, formatter, ns, target, label, fullmatch=None):
        mpsys = self.env[MultiProductSystem]
        if mpsys is not None:
            substeps = []
            prns = fullmatch.group('prns')
            if not prns:
                # Forwarded from _format_link, inherit current context
                product_id = fullmatch.group('it_' + ns) or \
                             fullmatch.group('prp')
                if product_id:
                    product_ns = 'product'
                    substeps = [product_id.strip()]
                elif isinstance(self.env, ProductEnvironment):
                    product_ns = 'product'
                    substeps = [self.env.product.prefix]
                else:
                    product_ns = 'global'
            elif prns == 'global:':
                product_ns = 'global'
            elif prns.startswith('product:'):
                product_ns, product_id = prns.strip().split(':')[:2]
                substeps = [product_id]
            report_id = fullmatch.group('prid') or \
                        re.match(r'^.*?(\d+)$', target).group(1)
            substeps += [ns, report_id]

            return mpsys._format_link(formatter, product_ns,
                                      u':'.join(substeps),
                                      label, fullmatch)
        else:
            return tag.a(label, class_='missing product')
