# coding: utf8
# Debian Contributors data source data mining tools for dak
#
# Copyright (C) 2013--2014  Enrico Zini <enrico@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
from ..types import Identifier
from .. import scanner
import datetime
import logging

log = logging.getLogger(__name__)

__all__ = ["Postgres"]

class Postgres(scanner.Scanner):
    """
    Perform data mining using a SQL query on a Postgres database.

    This requires python-psycopg2 to be installed.

    Example::

        contribution: uploader
        method: postgres
        db: service=projectb
        identifier: login
        query:
          SELECT s.install_date as date,
                 u.uid as id,
                 u.name as desc
            FROM source s
            JOIN fingerprint f ON s.sig_fpr = f.id
            JOIN uid u ON f.uid = u.id
        url: http://qa.debian.org/developer.php?login={id}&comaint=yes
    """

    db = scanner.CharField(blank=False, help_text="""
            database connection string. See `psycopg2.connect
            <http://initd.org/psycopg/docs/module.html#psycopg2.connect>`_
            for details.""")
    identifier = scanner.IdentifierTypeField(default="auto", help_text="""
            type of identifier that is found by this SQL query.
            """)
    query = scanner.CharField(blank=False, help_text="""
            SQL query used to list contributions. SELECT column field names are
            significant: ``id`` is the contributor name, email, or fingerprint,
            depending on how ``identifier`` is configured. ``date`` is the
            contribution date, as a date or datetime. ``desc`` (optional) is a
            human-readable description for this ``id``, like a person's name.
            All other SELECT columns are ignored, but can be useful to provide
            values for the ``url`` template.
            """)
    url = scanner.CharField(help_text="""
                            template used to build URLs to link to people's contributions.
                            Words in curly braces (like ``{id}``) will be
                            expanded with the SELECT column of the same name.
                            """)

    def scan(self):
        from .utils.mine import Aggregate
        import psycopg2
        import psycopg2.extensions
        import psycopg2.extras
        psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
        psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
        db = psycopg2.connect(self.db)
        db.set_client_encoding('UTF8')
        cur = db.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
        cur.execute(self.query)
        contribs = Aggregate()

        if self.url:
            url_by_id = {}
        else:
            url_by_id = None

        desc_by_id = {}

        for row in cur:
            # Validate id
            id = row.get("id", None)
            if not id:
                log.info("id is empty, skipping result row %r", row)
                continue

            # Validate date and turn it into a datetime.date
            date = row.get("date", None)
            if not date:
                log.info("date is empty, skipping result row %r", row)
                continue
            if isinstance(date, datetime.datetime):
                date = date.date()
            elif not isinstance(date, datetime.date):
                log.info("date is not a date I can understand, skipping result row %r", row)
                continue

            # Generate the URL if we didn't have one already
            if url_by_id is not None and id not in url_by_id:
                url_by_id[id] = self.url.format(**row)

            # Take note of desc if present
            desc = row.get("desc", None)
            if desc is not None:
                desc_by_id[id] = desc

            contribs.add(id, date)

        for id, (begin, end) in contribs.items():
            if self.identifier == "auto":
                try:
                    ident = Identifier.create_auto(id, default_desc=desc_by_id.get(id, None))
                except ValueError as e:
                    log.info("skipping identifier %s: %s", id, e)
            else:
                ident = Identifier(self.identifier, id, desc_by_id.get(id, None))
            yield ident, begin, end, url_by_id.get(id, None) if url_by_id else None
