# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import unittest
import datetime

from trytond.tests.test_tryton import activate_module, with_transaction
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.exceptions import UserError
from trytond import backend


class HistoryTestCase(unittest.TestCase):
    'Test History'

    @classmethod
    def setUpClass(cls):
        activate_module('tests')

    @with_transaction()
    def tearDown(self):
        pool = Pool()
        History = pool.get('test.history')
        HistoryLine = pool.get('test.history.line')
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        for Model in [History, HistoryLine]:
            table = Model.__table__()
            history_table = Model.__table_history__()
            cursor.execute(*table.delete())
            cursor.execute(*history_table.delete())
        transaction.commit()

    @with_transaction()
    def test_read(self):
        'Test read history'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()

        # Create some history entry
        # It is needed to commit to have different timestamps
        history = History(value=1)
        history.save()
        history_id = history.id
        first = history.create_date

        transaction.commit()

        history = History(history_id)
        history.value = 2
        history.save()
        second = history.write_date

        transaction.commit()

        history = History(history_id)
        history.value = 3
        history.save()
        third = history.write_date

        transaction.commit()

        for timestamp, value in [
                (first, 1),
                (second, 2),
                (third, 3),
                (datetime.datetime.now(), 3),
                (datetime.datetime.max, 3),
                ]:
            with Transaction().set_context(_datetime=timestamp):
                history = History(history_id)
                self.assertEqual(history.value, value)

        with Transaction().set_context(_datetime=datetime.datetime.min):
            self.assertRaises(UserError, History.read, [history_id])

    @unittest.skipUnless(backend.name() == 'postgresql',
        'CURRENT_TIMESTAMP as transaction_timestamp is specific to postgresql')
    @with_transaction()
    def test_read_same_timestamp(self):
        'Test read history with same timestamp'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()

        history = History(value=1)
        history.save()
        history_id = history.id
        first = history.create_date

        history.value = 2
        history.save()
        second = history.write_date

        self.assertEqual(first, second)

        transaction.commit()

        history = History(history_id)
        history.value = 3
        history.save()
        third = history.write_date

        transaction.commit()

        for timestamp, value in [
                (first, 2),
                (third, 3),
                ]:
            with Transaction().set_context(_datetime=timestamp):
                history = History(history_id)
                self.assertEqual(history.value, value)

    @with_transaction()
    def test_history_revisions(self):
        'Test history revisions'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()

        history = History(value=1)
        history.save()
        history_id = history.id
        first = history.create_date

        transaction.commit()

        history = History(history_id)
        history.value = 2
        history.save()
        second = history.write_date

        transaction.commit()

        history = History(history_id)
        history.value = 3
        history.save()
        third = history.write_date

        transaction.commit()

        revisions = History.history_revisions([history_id])
        self.assertEqual(revisions, [
                (third, history_id, 'Administrator'),
                (second, history_id, 'Administrator'),
                (first, history_id, 'Administrator'),
                ])

    @with_transaction()
    def test_restore_history(self):
        'Test restore history'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()

        history = History(value=1)
        history.save()
        history_id = history.id
        first = history.create_date

        transaction.commit()

        history = History(history_id)
        history.value = 2
        history.save()

        transaction.commit()

        History.restore_history([history_id], first)
        history = History(history_id)
        self.assertEqual(history.value, 1)

        transaction.rollback()

        History.restore_history([history_id], datetime.datetime.min)
        self.assertRaises(UserError, History.read, [history_id])

        transaction.rollback()

        History.delete([History(history_id)])

        transaction.commit()

        History.restore_history([history_id], datetime.datetime.max)
        self.assertRaises(UserError, History.read, [history_id])

    @with_transaction()
    def test_restore_history_before(self):
        'Test restore history before'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()

        history = History(value=1)
        history.save()
        history_id = history.id

        transaction.commit()

        history = History(history_id)
        history.value = 2
        history.save()
        second = history.write_date

        transaction.commit()

        history = History(history_id)
        history.value = 3
        history.save()

        transaction.commit()

        History.restore_history_before([history_id], second)
        history = History(history_id)
        self.assertEqual(history.value, 1)

    @unittest.skipUnless(backend.name() == 'postgresql',
        'CURRENT_TIMESTAMP as transaction_timestamp is specific to postgresql')
    @with_transaction()
    def test_restore_history_same_timestamp(self):
        'Test restore history with same timestamp'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()

        history = History(value=1)
        history.save()
        history_id = history.id
        first = history.create_date
        history.value = 2
        history.save()
        second = history.create_date

        self.assertEqual(first, second)

        transaction.commit()

        history = History(history_id)
        history.value = 3
        history.save()

        transaction.commit()

        History.restore_history([history_id], first)
        history = History(history_id)
        self.assertEqual(history.value, 2)

    @with_transaction()
    def test_ordered_search(self):
        'Test ordered search of history models'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()
        order = [('value', 'ASC')]

        history = History(value=1)
        history.save()
        first_id = history.id
        first_stamp = history.create_date
        transaction.commit()

        history = History(value=2)
        history.save()
        second_id = history.id
        second_stamp = history.create_date

        transaction.commit()

        first, second = History.search([], order=order)

        self.assertEqual(first.id, first_id)
        self.assertEqual(second.id, second_id)

        first.value = 3
        first.save()
        third_stamp = first.write_date
        transaction.commit()

        results = [
            (first_stamp, [first]),
            (second_stamp, [first, second]),
            (third_stamp, [second, first]),
            (datetime.datetime.now(), [second, first]),
            (datetime.datetime.max, [second, first]),
            ]
        for timestamp, instances in results:
            with Transaction().set_context(_datetime=timestamp):
                records = History.search([], order=order)
                self.assertEqual(records, instances)
            transaction.rollback()

        to_delete, _ = History.search([], order=order)

        self.assertEqual(to_delete.id, second.id)

        History.delete([to_delete])
        transaction.commit()

        results = [
            (first_stamp, [first]),
            (second_stamp, [first, second]),
            (third_stamp, [second, first]),
            (datetime.datetime.now(), [first]),
            (datetime.datetime.max, [first]),
            ]
        for timestamp, instances in results:
            with Transaction().set_context(_datetime=timestamp,
                    from_test=True):
                records = History.search([], order=order)
                self.assertEqual(records, instances)
            transaction.rollback()

    @unittest.skipUnless(backend.name() == 'postgresql',
        'CURRENT_TIMESTAMP as transaction_timestamp is specific to postgresql')
    @with_transaction()
    def test_ordered_search_same_timestamp(self):
        'Test ordered search  with same timestamp'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()
        order = [('value', 'ASC')]

        history = History(value=1)
        history.save()
        first_stamp = history.create_date
        history.value = 4
        history.save()
        second_stamp = history.write_date

        self.assertEqual(first_stamp, second_stamp)
        transaction.commit()

        results = [
            (second_stamp, [history], [4]),
            (datetime.datetime.now(), [history], [4]),
            (datetime.datetime.max, [history], [4]),
            ]

        for timestamp, instances, values in results:
            with Transaction().set_context(_datetime=timestamp,
                    last_test=True):
                records = History.search([], order=order)
                self.assertEqual(records, instances)
                self.assertEqual([x.value for x in records], values)
            transaction.rollback()

    @with_transaction()
    def test_ordered_search_nested(self):
        "Test ordered search nested"
        pool = Pool()
        History = pool.get('test.history')
        HistoryLine = pool.get('test.history.line')
        transaction = Transaction()
        order = [('history.value', 'ASC')]

        history = History(value=1)
        history.save()
        history2 = History(value=2)
        history2.save()
        line = HistoryLine(history=history)
        line.save()
        line2 = HistoryLine(history=history2)
        line2.save()
        first_stamp = line2.create_date
        transaction.commit()

        history.value = 3
        history.save()
        second_stamp = history.write_date
        transaction.commit()

        results = [
            (first_stamp, [line, line2]),
            (second_stamp, [line2, line]),
            ]
        for timestamp, instances in results:
            with Transaction().set_context(_datetime=timestamp):
                records = HistoryLine.search([], order=order)
                self.assertListEqual(records, instances)

    @with_transaction()
    def test_browse(self):
        'Test browsing history'
        pool = Pool()
        History = pool.get('test.history')
        Line = pool.get('test.history.line')
        transaction = Transaction()

        history = History(value=1)
        history.save()
        history_id = history.id
        line_a = Line(name='a', history=history)
        line_a.save()
        line_a_id = line_a.id
        line_b = Line(name='b', history=history)
        line_b.save()
        line_b_id = line_b.id

        first_stamp = line_b.create_date

        history.stamp = first_stamp
        history.save()

        transaction.commit()

        history = History(history_id)
        history.value = 2
        history.save()

        Line.delete([Line(line_b_id)])

        line_a = Line(line_a_id)
        line_a.name = 'c'
        line_a.save()

        second_stamp = line_a.write_date

        transaction.commit()

        history = History(history_id)
        self.assertEqual(history.value, 2)
        self.assertEqual([l.name for l in history.lines], ['c'])
        self.assertEqual(history.stamp, first_stamp)
        self.assertEqual(
            [l.name for l in history.lines_at_stamp], ['a', 'b'])

        with Transaction().set_context(_datetime=first_stamp):
            history = History(history_id)
        self.assertEqual(history.value, 1)
        self.assertEqual([l.name for l in history.lines], ['a', 'b'])

        with Transaction().set_context(_datetime=second_stamp):
            history = History(history_id)
        self.assertEqual(history.value, 2)
        self.assertEqual([l.name for l in history.lines], ['c'])
        self.assertEqual(history.stamp, first_stamp)
        self.assertEqual(
            [l.name for l in history.lines_at_stamp], ['a', 'b'])

    @with_transaction()
    def test_search_cursor_max(self):
        'Test search with number of history entries at database.IN_MAX'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()
        database = transaction.database

        history = History(value=-1)
        history.save()

        for history.value in range(database.IN_MAX + 1):
            history.save()

        with transaction.set_context(_datetime=datetime.datetime.max):
            record, = History.search([])

            self.assertEqual(record.value, database.IN_MAX)

    @with_transaction()
    def test_search_cursor_max_entries(self):
        'Test search for skipping first history entries at database.IN_MAX'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()
        database = transaction.database

        for i in range(0, 2):
            history = History(value=-1)
            history.save()

            for history.value in range(database.IN_MAX + 1):
                history.save()

        with transaction.set_context(_datetime=datetime.datetime.max):
            records = History.search([])

            self.assertEqual({r.value for r in records}, {database.IN_MAX})
            self.assertEqual(len(records), 2)

    @with_transaction()
    def test_search_cursor_max_histories(self):
        'Test search with number of histories at database.IN_MAX'
        pool = Pool()
        History = pool.get('test.history')
        transaction = Transaction()
        database = transaction.database

        n = database.IN_MAX + 1
        History.create([{'value': 1}] * n)

        with transaction.set_context(_datetime=datetime.datetime.max):
            records = History.search([])

            self.assertEqual({r.value for r in records}, {1})
            self.assertEqual(len(records), n)


def suite():
    return unittest.TestLoader().loadTestsFromTestCase(HistoryTestCase)
