from __future__ import absolute_import
try:
    from django.db.backends import BaseDatabaseOperations
except ImportError:
    from django.db.backends.base.operations import BaseDatabaseOperations

#from access.pyodbc import query
from . import query

from .debug import _DebugOutput


import datetime
import time
import decimal
# import six

class DatabaseOperations(BaseDatabaseOperations):
   #compiler_module = "access.pyodbc.compiler"
    compiler_module = "django-msaccess.compiler"
    def __init__(self, connection):
        super(DatabaseOperations, self).__init__(connection)
        self._ss_ver = None

    def _get_sql_server_ver(self, connection=None):
        """
        Returns the version of the SQL Server in use:
        """
        # if self._ss_ver is not None:
            # return self._ss_ver
        # else:
            # if connection:
                # cur = connection.cursor()
            # else:
                # from django.db import connection
                # cur = connection.cursor()
            # cur.execute("SELECT CAST(SERVERPROPERTY('ProductVersion') as varchar)")
            # ver_code = int(cur.fetchone()[0].split('.')[0])
            # if ver_code >= 10:
                # self._ss_ver = 2008
            # elif ver_code == 9:
                # self._ss_ver = 2005
            # else:
                # self._ss_ver = 2000
        return None
    sql_server_ver = property(_get_sql_server_ver)

    def date_extract_sql(self, lookup_type, field_name):
        """
        Given a lookup_type of 'year', 'month', 'day' or 'week_day', returns
        the SQL that extracts a value from the given date field field_name.
        """
        lookup_type_conversions = { "year":"'yyyy'", "month":"'m'", "day":"'d'", "week_day":"'w'" }
        #if lookup_type == 'week_day':
        #    return "DATEPART(dw, %s)" % field_name
        #else:
        return "DATEPART(%s, %s)" % (lookup_type_conversions[lookup_type], field_name)

    def date_trunc_sql(self, lookup_type, field_name):
        """
        Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
        truncates the given date field field_name to a DATE object with only
        the given specificity.
        """
        if lookup_type == 'year':
            return "CDATE(CSTR(DATEPART('yyyy', %s)) + '/01/01')" % field_name
        if lookup_type == 'month':
            return "CDATE(CSTR(DATEPART('yyyy', %s)) + '/' + CSTR(DATEPART('m', %s)) + '/01')" % (field_name, field_name)
        if lookup_type == 'day':
            return "%s" % field_name

#BUG def field_cast_sql(self, db_type):
    def field_cast_sql(self, db_type, internal_type):
        #       db_type= varchar(150)
        # internal_type= CharField
        fnm='[DatabaseOperations.field_cast_sql(operations.py:MS-Access)]'
        _DebugOutput(fnm,f"db_type={db_type}")
        _DebugOutput(fnm,f"db_type={db_type.__class__}")
        _DebugOutput(fnm,f"internal_type={internal_type}")
        _DebugOutput(fnm,f"internal_type={internal_type.__class__}")
        """
        Given a column type (e.g. 'BLOB', 'VARCHAR'), returns the SQL necessary
        to cast it before using it in a WHERE statement. Note that the
        resulting string should contain a '%s' placeholder for the column being
        searched against.
        """        
        # if self.sql_server_ver < 2005 and db_type and db_type.lower() == 'ntext':
        #     return 'CAST(%s as nvarchar)'
        _DebugOutput(fnm," return '%s'")
        return '%s'



    def fulltext_search_sql(self, field_name):
        """
        Returns the SQL WHERE clause to use in order to perform a full-text
        search of the given field_name. Note that the resulting string should
        contain a '%s' placeholder for the value being searched against.
        """
        return 'CONTAINS(%s, %%s)' % field_name

    def last_insert_id(self, cursor, table_name, pk_name):
        """
        Given a cursor object that has just performed an INSERT statement into
        a table that has an auto-incrementing ID, returns the newly created ID.

        This method also receives the table name and the name of the primary-key
        column.
        """
        # TODO: Check how the `last_insert_id` is being used in the upper layers
        #       in context of multithreaded access, compare with other backends

        # IDENT_CURRENT:  http://msdn2.microsoft.com/en-us/library/ms175098.aspx
        # SCOPE_IDENTITY: http://msdn2.microsoft.com/en-us/library/ms190315.aspx
        # @@IDENTITY:     http://msdn2.microsoft.com/en-us/library/ms187342.aspx

        # IDENT_CURRENT is not limited by scope and session; it is limited to
        # a specified table. IDENT_CURRENT returns the value generated for
        # a specific table in any session and any scope.
        # SCOPE_IDENTITY and @@IDENTITY return the last identity values that
        # are generated in any table in the current session. However,
        # SCOPE_IDENTITY returns values inserted only within the current scope;
        # @@IDENTITY is not limited to a specific scope.

        table_name = self.quote_name(table_name)
        cursor.execute("SELECT MAX(CLNG(%s)) FROM %s" % (pk_name, table_name))
        return cursor.fetchone()[0]

#BUG def lookup_cast(self, lookup_type):
    def lookup_cast(self, lookup_type, internal_type=None):

        if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'):
            return "UCASE(%s)"
        return "%s"

    def query_class(self, DefaultQueryClass):
        """
        Given the default Query class, returns a custom Query class
        to use for this backend. Returns None if a custom Query isn't used.
        See also BaseDatabaseFeatures.uses_custom_query_class, which regulates
        whether this method is called at all.
        """
        return query.query_class(DefaultQueryClass)

    def quote_name(self, name):
        """
        Returns a quoted version of the given table, index or column name. Does
        not quote the given name if it's already been quoted.
        """
        if name.startswith('[') and name.endswith(']'):
            return name # Quoting once is enough.
        return '[%s]' % name

    def random_function_sql(self):
        """
        Returns a SQL expression that returns a random value.
        """
        return "RAND()"

    def last_executed_query(self, cursor, sql, params):
        """
        Returns a string of the query last executed by the given cursor, with
        placeholders replaced with actual values.

        `sql` is the raw query containing placeholders, and `params` is the
        sequence of parameters. These are used by default, but this method
        exists for database backends to provide a better implementation
        according to their own quoting schemes.
        """
        return super(DatabaseOperations, self).last_executed_query(cursor, cursor.last_sql, cursor.last_params)

    #def savepoint_create_sql(self, sid):
    #    """
    #    Returns the SQL for starting a new savepoint. Only required if the
    #    "uses_savepoints" feature is True. The "sid" parameter is a string
    #    for the savepoint id.
    #    """
    #    return "SAVE TRANSACTION %s" % sid

    #def savepoint_commit_sql(self, sid):
    #    """
    #    Returns the SQL for committing the given savepoint.
    #    """
    #    return "COMMIT TRANSACTION %s" % sid

    #def savepoint_rollback_sql(self, sid):
    #    """
    #    Returns the SQL for rolling back the given savepoint.
    #    """
    #    return "ROLLBACK TRANSACTION %s" % sid

    def sql_flush(self, style, tables, sequences):
        """
        Returns a list of SQL statements required to remove all data from
        the given database tables (without actually removing the tables
        themselves).

        The `style` argument is a Style object as returned by either
        color_style() or no_style() in django.core.management.color.
        """
        if tables:
            # Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY
            # So must use the much slower DELETE
            from django.db import connection
            cursor = connection.cursor()
            # Try to minimize the risks of the braindeaded inconsistency in
            # DBCC CHEKIDENT(table, RESEED, n) behavior.
            seqs = []
            for seq in sequences:
                cursor.execute("SELECT COUNT(*) FROM %s" % self.quote_name(seq["table"]))
                rowcnt = cursor.fetchone()[0]
                elem = {}
                if rowcnt:
                    elem['start_id'] = 0
                else:
                    elem['start_id'] = 1
                elem.update(seq)
                seqs.append(elem)
            cursor.execute("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS")
            fks = cursor.fetchall()
            sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' % \
                    (self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
            sql_list.extend(['%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
                             style.SQL_FIELD(self.quote_name(table)) ) for table in tables])
            # Then reset the counters on each table.
            sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
                style.SQL_KEYWORD('DBCC'),
                style.SQL_KEYWORD('CHECKIDENT'),
                style.SQL_FIELD(self.quote_name(seq["table"])),
                style.SQL_KEYWORD('RESEED'),
                style.SQL_FIELD('%d' % seq['start_id']),
                style.SQL_KEYWORD('WITH'),
                style.SQL_KEYWORD('NO_INFOMSGS'),
                ) for seq in seqs])
            sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' % \
                    (self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
            return sql_list
        else:
            return []

    #def sequence_reset_sql(self, style, model_list):
    #    """
    #    Returns a list of the SQL statements required to reset sequences for
    #    the given models.
    #
    #    The `style` argument is a Style object as returned by either
    #    color_style() or no_style() in django.core.management.color.
    #    """
    #    from django.db import models
    #    output = []
    #    for model in model_list:
    #        for f in model._meta.local_fields:
    #            if isinstance(f, models.AutoField):
    #                output.append(...)
    #                break # Only one AutoField is allowed per model, so don't bother continuing.
    #        for f in model._meta.many_to_many:
    #            output.append(...)
    #    return output

    def start_transaction_sql(self):
        """
        Returns the SQL statement required to start a transaction.
        """
        return "BEGIN TRANSACTION"

    def sql_for_tablespace(self, tablespace, inline=False):
        """
        Returns the SQL that will be appended to tables or rows to define
        a tablespace. Returns '' if the backend doesn't use tablespaces.
        """
        return "ON %s" % self.quote_name(tablespace)

    def prep_for_like_query(self, x):
        """Prepares a value for use in a LIKE query."""
       #from django.utils.encoding import smart_unicode
        from django.utils.encoding import smart_str
        # http://msdn2.microsoft.com/en-us/library/ms179859.aspx
       #return smart_unicode(x).replace('\\', '\\\\').replace('[', '[[]').replace('%', '[%]').replace('_', '[_]')
        return smart_str(x).replace('\\', '\\\\').replace('[', '[[]').replace('%', '[%]').replace('_', '[_]')

    def prep_for_iexact_query(self, x):
        """
        Same as prep_for_like_query(), but called for "iexact" matches, which
        need not necessarily be implemented using "LIKE" in the backend.
        """
        return x

    def value_to_db_datetime(self, value):
        """
        Transform a datetime value to an object compatible with what is expected
        by the backend driver for datetime columns.
        """
        if value is None:
            return None
        # SQL Server doesn't support microseconds
        return value.replace(microsecond=0)

    def value_to_db_time(self, value):
        """
        Transform a time value to an object compatible with what is expected
        by the backend driver for time columns.
        """
        if value is None:
            return None
        # SQL Server doesn't support microseconds
        # if isinstance(value, six.string_types):
        #     return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
        if isinstance(value, str):
            return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
        return datetime.datetime(1900, 1, 1, value.hour, value.minute, value.second)

    def year_lookup_bounds(self, value):
        """
        Returns a two-elements list with the lower and upper bound to be used
        with a BETWEEN operator to query a field value using a year lookup

        `value` is an int, containing the looked-up year.
        """
        first = '%s-01-01 00:00:00'
        # SQL Server doesn't support microseconds
        last = '%s-12-31 23:59:59'
        return [first % value, last % value]
    def value_to_db_decimal(self, value, max_digits, decimal_places):
        """
        Transform a decimal.Decimal value to an object compatible with what is
        expected by the backend driver for decimal (numeric) columns.
        """
        if value is None:
            return None
        if isinstance(value, decimal.Decimal):
            context = decimal.getcontext().copy()
            context.prec = max_digits
            return u"%.*f" % (decimal_places, value.quantize(decimal.Decimal(".1") ** decimal_places, context=context))
        else:
            return u"%.*f" % (decimal_places, value)

    def bulk_insert_sql(self, fields, placeholder_rows):
        fnm='[DatabaseOperations.bulk_insert_sql(operations.py:MS-Access)]'

        #fields=(<django.db.models.fields.CharField: session_key>, <django.db.models.fields.TextField: session_data>, <django.db.models.fields.DateTimeField: expire_date>)
        #placeholder_rows=(('%s', '%s', '%s'),)
        _DebugOutput(fnm,f"fields={fields}")
        _DebugOutput(fnm,f"placeholder_rows={placeholder_rows}")

        #
        # mysql
        # 
        placeholder_rows_sql = (", ".join(row) for row in placeholder_rows)
        values_sql = ", ".join("(%s)" % sql for sql in placeholder_rows_sql)
        ret = "VALUES " + values_sql
        #print(f"[bulk_insert_sql(DatabaseOperations)]ret={ret}")
        _DebugOutput(fnm,f"ret={ret}")

        #ret=VALUES (%s, %s, %s)
        #return "VALUES " + values_sql
        return ret


