from django.db import transaction
from denorm.db import base

import logging


logger = logging.getLogger('denorm-sqlite')


class RandomBigInt(base.RandomBigInt):
    def sql(self):
        return 'RANDOM()'


class TriggerNestedSelect(base.TriggerNestedSelect):

    def sql(self):
        columns = self.columns
        table = self.table
        where = ", ".join(["%s = %s" % (k, v) for k, v in self.kwargs.items()])
        return 'SELECT DISTINCT %(columns)s FROM %(table)s WHERE %(where)s' % locals(), tuple()


class TriggerActionInsert(base.TriggerActionInsert):

    def sql(self):
        table = self.model._meta.db_table
        columns = "(" + ", ".join(self.columns) + ")"
        if isinstance(self.values, TriggerNestedSelect):
            sql, params = self.values.sql()
            values = "" + sql + ""
        else:
            values = "VALUES(" + ", ".join(self.values) + ")"
            params = []

        return 'INSERT OR REPLACE INTO %(table)s %(columns)s %(values)s' % locals(), tuple(params)


class TriggerActionUpdate(base.TriggerActionUpdate):

    def sql(self):
        table = self.model._meta.db_table
        updates = ", ".join(["%s = %s" % (k, v) for k, v in zip(self.columns, self.values)])
        if isinstance(self.where, tuple):
            where, where_params = self.where
        else:
            where, where_params = self.where, []

        return 'UPDATE %(table)s SET %(updates)s WHERE %(where)s' % locals(), where_params


class Trigger(base.Trigger):

    def name(self):
        name = base.Trigger.name(self)
        if self.content_type_field:
            name += "_%s" % self.content_type
        return name

    def sql(self):
        qn = self.connection.ops.quote_name

        name = self.name()
        params = []
        action_list = []
        actions_added = set()
        for a in self.actions:
            sql, action_params = a.sql()
            if sql:
                if not sql.endswith(';'):
                    sql += ';'
                action_params = tuple(action_params)
                if (sql, action_params) not in actions_added:
                    actions_added.add((sql, action_params))
                    action_list.extend(sql.split('\n'))
                    params.extend(action_params)
        actions = "\n        ".join(action_list)
        table = self.db_table
        time = self.time.upper()
        event = self.event.upper()
        content_type = self.content_type
        ct_field = self.content_type_field

        when = []
        if event == "UPDATE":
            when.append("(" + "OR".join(["(OLD.%s IS NOT NEW.%s)" % (qn(f), qn(f)) for f, t in self.fields]) + ")")
        if ct_field:
            ct_field = qn(ct_field)
            if event == "DELETE":
                when.append("(OLD.%s == %s)" % (ct_field, content_type))
            elif event == "INSERT":
                when.append("(NEW.%s == %s)" % (ct_field, content_type))
            elif event == "UPDATE":
                when.append("((OLD.%(ctf)s == %(ct)s) OR (NEW.%(ctf)s == %(ct)s))" % {'ctf': ct_field, 'ct': content_type})

        when = "AND".join(when)
        if when:
            when = "WHEN(%s)" % (when,)

        return """
CREATE TRIGGER %(name)s
    %(time)s %(event)s ON %(table)s
    FOR EACH ROW %(when)s BEGIN
        %(actions)s
    END;
""" % locals(), tuple(params)


class TriggerSet(base.TriggerSet):
    def drop_atomic(self):
        qn = self.connection.ops.quote_name
        cursor = self.cursor()

        cursor.execute("SELECT name, tbl_name FROM sqlite_master WHERE type = 'trigger' AND name LIKE 'denorm_%%';")
        for trigger_name, table_name in cursor.fetchall():
            cursor.execute("DROP TRIGGER %s;" % (qn(trigger_name),))

    def drop(self):
        try:
            with transaction.atomic():
                self.drop_atomic()
        except AttributeError:
            self.drop_atomic()
            transaction.commit_unless_managed(using=self.using)

    def install_atomic(self):
        cursor = self.cursor()

        for name, trigger in self.triggers.items():
            sql, args = trigger.sql()
            cursor.execute(sql, args)

    def install(self):
        try:
            with transaction.atomic():
                self.install_atomic()
        except AttributeError:
            self.install_atomic()
            transaction.commit_unless_managed(using=self.using)
