# odbc test suite kindly contributed by Frank Millman.
import odbc
import os
import sys
import tempfile
import unittest

import pytest
import pythoncom
from pywin32_testutil import str2bytes, str2memory, TestSkipped
from win32com.client import constants
from win32com.client.gencache import EnsureDispatch


class TestStuff(unittest.TestCase):
    def setUp(self):
        self.tablename = "pywin32test_users"
        self.db_filename = None
        self.conn = self.cur = None
        try:
            # Test any database if a connection string is supplied...
            conn_str = os.environ['TEST_ODBC_CONNECTION_STRING']
        except KeyError:
            # Create a local MSAccess DB for testing.
            self.db_filename = tempfile.NamedTemporaryFile().name + '.mdb'

            # Create a brand-new database - what is the story with these?
            for suffix in (".36", ".35", ".30"):
                try:
                    dbe = EnsureDispatch("DAO.DBEngine" + suffix)
                    break
                except pythoncom.com_error:
                    pass
            else:
                raise TestSkipped("Can't find a DB engine")

            workspace = dbe.Workspaces(0)

            newdb = workspace.CreateDatabase(self.db_filename,
                                             constants.dbLangGeneral,
                                             constants.dbEncrypt)

            newdb.Close()

            conn_str = "Driver={Microsoft Access Driver (*.mdb)};dbq=%s;Uid=;Pwd=;" \
                       % (self.db_filename,)
        # print 'Connection string:', conn_str
        self.conn = odbc.odbc(conn_str)
        # And we expect a 'users' table for these tests.
        self.cur = self.conn.cursor()
        # self.cur.setoutputsize(1000)
        try:
            self.cur.execute("""drop table %s""" % self.tablename)
        except (odbc.error, odbc.progError):
            pass

        # This needs to be adjusted for sql server syntax for unicode fields
        # - memo -> TEXT
        # - varchar -> nvarchar
        assert self.cur.execute(
            """create table %s (
                    userid varchar(25),
                    username varchar(25),
                    bitfield bit,
                    intfield integer,
                    floatfield float,
                    datefield datetime,
                    rawfield varbinary(100),
                    longtextfield memo,
                    longbinaryfield image
            )""" % self.tablename) == -1

    def tearDown(self):
        if self.cur is not None:
            try:
                self.cur.execute("""drop table %s""" % self.tablename)
            except (odbc.error, odbc.progError) as why:
                print("Failed to delete test table %s" % self.tablename, why)

            self.cur.close()
            self.cur = None
        if self.conn is not None:
            self.conn.close()
            self.conn = None
        if self.db_filename is not None:
            try:
                os.unlink(self.db_filename)
            except OSError:
                pass

    @pytest.mark.xfail
    def test_insert_select(self, userid='Frank', username='Frank Millman'):
        assert self.cur.execute("insert into %s (userid, username) \
            values (?,?)" % self.tablename, [userid, username]) == 1
        assert self.cur.execute("select * from %s \
            where userid = ?" % self.tablename, [userid.lower()]) == 0
        assert self.cur.execute("select * from %s \
            where username = ?" % self.tablename, [username.lower()]) == 0

    @pytest.mark.xfail
    def test_insert_select_unicode(
            self, userid='Frank', username="Frank Millman"):
        assert self.cur.execute("insert into %s (userid, username)\
            values (?,?)" % self.tablename, [userid, username]) == 1
        assert self.cur.execute("select * from %s \
            where userid = ?" % self.tablename, [userid.lower()]) == 0
        assert self.cur.execute("select * from %s \
            where username = ?" % self.tablename, [username.lower()]) == 0

    @pytest.mark.xfail
    def test_insert_select_unicode_ext(self):
        userid = "t-\xe0\xf2"
        username = "test-\xe0\xf2 name"
        self.test_insert_select_unicode(userid, username)

    def _test_val(self, fieldName, value):
        for x in range(100):
            self.cur.execute(
                "delete from %s where userid='Frank'" %
                self.tablename)
            assert self.cur.execute(
                "insert into %s (userid, %s) values (?,?)" % (
                    self.tablename, fieldName),
                ["Frank", value]) == 1
            self.cur.execute(
                "select %s from %s where userid = ?" %
                (fieldName, self.tablename), ["Frank"])
            rows = self.cur.fetchmany()
            assert 1 == len(rows)
            row = rows[0]
            assert row[0] == value

    @pytest.mark.xfail
    def testBit(self):
        self._test_val('bitfield', 1)
        self._test_val('bitfield', 0)

    @pytest.mark.xfail
    def testInt(self):
        self._test_val('intfield', 1)
        self._test_val('intfield', 0)
        try:
            big = sys.maxsize
        except AttributeError:
            big = sys.maxsize
        self._test_val('intfield', big)

    @pytest.mark.xfail
    def testFloat(self):
        self._test_val('floatfield', 1.01)
        self._test_val('floatfield', 0)

    @pytest.mark.xfail
    def testVarchar(self, ):
        self._test_val('username', 'foo')

    @pytest.mark.xfail
    def testLongVarchar(self):
        """ Test a long text field in excess of internal cursor data size (65536)"""
        self._test_val('longtextfield', 'abc' * 70000)

    @pytest.mark.xfail
    def testLongBinary(self):
        """ Test a long raw field in excess of internal cursor data size (65536)"""
        self._test_val('longbinaryfield', str2memory('\0\1\2' * 70000))

    @pytest.mark.xfail
    def testRaw(self):
        # Test binary data
        self._test_val('rawfield', str2memory('\1\2\3\4\0\5\6\7\8'))

    @pytest.mark.xfail
    def test_widechar(self):
        """Test a unicode character that would be mangled if bound as plain character.
            For example, previously the below was returned as ascii 'a'
        """
        self._test_val('username', '\u0101')

    @pytest.mark.xfail
    def testDates(self):
        import datetime
        for v in (
                (1900, 12, 25, 23, 39, 59),
        ):
            d = datetime.datetime(*v)
            self._test_val('datefield', d)

    @pytest.mark.xfail
    def test_set_nonzero_length(self):
        assert self.cur.execute(
                "insert into %s (userid,username) "
                "values (?,?)" %
                self.tablename, [
                'Frank', 'Frank Millman']) == 1
        assert self.cur.execute(
                "update %s set username = ?" %
                self.tablename, ['Frank']) == 1
        assert self.cur.execute(
                "select * from %s" %
                self.tablename) == 0
        assert len(self.cur.fetchone()[1]) == 5

    @pytest.mark.xfail
    def test_set_zero_length(self):
        assert self.cur.execute(
                "insert into %s (userid,username) "
                "values (?,?)" %
                self.tablename, [
                str2bytes('Frank'), '']) == 1
        assert self.cur.execute(
                "select * from %s" %
                self.tablename) == 0
        assert len(self.cur.fetchone()[1]) == 0

    @pytest.mark.xfail
    def test_set_zero_length_unicode(self):
        assert self.cur.execute(
                "insert into %s (userid,username) "
                "values (?,?)" %
                self.tablename, [
                'Frank', '']) == 1
        assert self.cur.execute(
                "select * from %s" %
                self.tablename) == 0
        assert len(self.cur.fetchone()[1]) == 0


if __name__ == '__main__':
    unittest.main()
