import unittest
import logging
import sys
if sys.version[0] == '2':
    import Queue as queue
else:
    import queue as queue
import pika
import esgfpid.rabbit.asynchronous.thread_feeder
from esgfpid.rabbit.asynchronous.exceptions import OperationNotAllowed

LOGGER = logging.getLogger(__name__)
LOGGER.addHandler(logging.NullHandler())

# Test resources:
from resources.TESTVALUES import *
import resources.TESTVALUES as TESTHELPERS

class ThreadFeederTestCase(unittest.TestCase):

    def setUp(self):
        LOGGER.info('######## Next test (%s) ##########', __name__)

    def tearDown(self):
        LOGGER.info('#############################')

    def make_feeder(self, error=None):

        thread = TESTHELPERS.get_thread_mock2(error)
        statemachine = esgfpid.rabbit.asynchronous.thread_statemachine.StateMachine()
        nodemanager = TESTHELPERS.get_nodemanager()

        feeder = esgfpid.rabbit.asynchronous.thread_feeder.RabbitFeeder(
            thread,
            statemachine,
            nodemanager)

        statemachine.set_to_available()
        nodemanager.set_next_host() # otherwise, we cannot call its method inside the publish method
        return feeder, thread

    # Tests

    #
    # Sending messages
    #

    def test_send_message_ok(self):

        # Preparation:
        msg = "{'foo':'bar'}"
        feeder, thread = self.make_feeder()
        thread.messages.append(msg)

        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        # Publish was called:
        thread._channel.basic_publish.assert_called_once()
        # Message not waiting in queue anymore:
        self.assertNotIn(msg, thread.messages)

    def test_routing_key_ok(self):

        # Preparation:
        msg = '{"foo":"bar", "ROUTING_KEY":"myprefix.HASH.fresh.mydescription"}'
        feeder, thread = self.make_feeder()
        thread.messages.append(msg)

        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        # Publish was called:
        thread._channel.basic_publish.assert_called_once()
        # Message not waiting in queue anymore:
        self.assertNotIn(msg, thread.messages)
        # Correct routing key:
        rk = thread._channel.basic_publish.call_args[1]["routing_key"]
        self.assertEqual(rk, 'myprefix.HASH.fresh.mydescription')

    def test_routing_key_untrusted_ok(self):

        # Preparation:
        msg = '{"foo":"bar", "ROUTING_KEY":"myprefix.HASH.fresh.mydescription"}'
        feeder, thread = self.make_feeder()
        feeder.nodemanager._NodeManager__current_node['is_open']=True
        thread.messages.append(msg)

        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        # Publish was called:
        thread._channel.basic_publish.assert_called_once()
        # Message not waiting in queue anymore:
        self.assertNotIn(msg, thread.messages)
        # Correct routing key:
        rk = thread._channel.basic_publish.call_args[1]["routing_key"]
        self.assertEqual(rk, 'myprefix.HASH.fresh-untrusted-fallback.mydescription')

    def test_routing_key_untrusted_ok(self):

        # Preparation:
        msg = '{"foo":"bar", "ROUTING_KEY":"myprefix.HASH.fresh.mydescription"}'
        feeder, thread = self.make_feeder()
        feeder.nodemanager._NodeManager__current_node['is_open']=True
        feeder.nodemanager._NodeManager__has_trusted=False
        thread.messages.append(msg)

        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        # Publish was called:
        thread._channel.basic_publish.assert_called_once()
        # Message not waiting in queue anymore:
        self.assertNotIn(msg, thread.messages)
        # Correct routing key:
        rk = thread._channel.basic_publish.call_args[1]["routing_key"]
        self.assertEqual(rk, 'myprefix.HASH.fresh-untrusted-only.mydescription')

    def test_send_message_empty(self):

        # Preparation:
        feeder, thread = self.make_feeder()

        # Run code to be tested:
        feeder.publish_message()
        feeder.publish_message()

        # Check result:
        # Publish was not called:
        thread._channel.basic_publish.assert_not_called()
  

    def test_send_message_error(self):

        # Preparation:
        msg = "{'foo':'bar'}"
        feeder, thread = self.make_feeder(error=pika.exceptions.ChannelClosed(reply_code=0, reply_text='Channel Closed'))
        thread.messages.append(msg)

        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        # Publish was tried:
        thread._channel.basic_publish.assert_called_once()
        # Message was put back to queue:
        self.assertIn(msg, thread.messages)
        self.assertIn(msg, thread.put_back)

    def test_send_message_NOT_STARTED_YET(self):

        # Preparation:
        msg = "{'foo':'bar'}"
        feeder, thread = self.make_feeder()
        thread.messages.append(msg)
        feeder.statemachine._StateMachine__state = 0
        
        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        self.assertTrue(feeder.statemachine.is_NOT_STARTED_YET())
        self.assertIn(msg, thread.messages)
        thread._channel.basic_publish.assert_not_called()

    def test_send_message_PERMANENTLY_UNAVAILABLE_1(self):

        # Preparation:
        msg = "{'foo':'bar'}"
        feeder, thread = self.make_feeder()
        thread.messages.append(msg)
        feeder.statemachine.set_to_permanently_unavailable()
        feeder.statemachine.set_detail_closed_by_publisher()

        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        self.assertTrue(feeder.statemachine.is_PERMANENTLY_UNAVAILABLE())
        self.assertIn(msg, thread.messages)
        thread._channel.basic_publish.assert_not_called()

    def test_send_message_PERMANENTLY_UNAVAILABLE_2(self):

        # Preparation:
        msg = "{'foo':'bar'}"
        feeder, thread = self.make_feeder()
        thread.messages.append(msg)
        feeder.statemachine.set_to_permanently_unavailable()
        feeder.statemachine.detail_could_not_connect = True

        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        self.assertTrue(feeder.statemachine.is_PERMANENTLY_UNAVAILABLE())
        self.assertIn(msg, thread.messages)
        thread._channel.basic_publish.assert_not_called()

    def test_send_message_WAITING_TO_BE_AVAILABLE(self):

        # Preparation:
        msg = "{'foo':'bar'}"
        feeder, thread = self.make_feeder()
        thread.messages.append(msg)
        feeder.statemachine.set_to_waiting_to_be_available()
        
        # Run code to be tested:
        feeder.publish_message()

        # Check result:
        self.assertIn(msg, thread.messages)
        self.assertTrue(feeder.statemachine.is_WAITING_TO_BE_AVAILABLE())
        thread._channel.basic_publish.assert_not_called()

    def test_reset_delivery_number(self):

        # Preparation:
        msg = "{'foo':'bar'}"
        feeder, thread = self.make_feeder()
        thread.messages.append(msg)
        thread.messages.append(msg)
        thread.messages.append(msg)

        # Pre-Check:
        self.assertEqual(feeder._RabbitFeeder__delivery_number, 1)

        # Increase delivery number:
        feeder.publish_message()
        feeder.publish_message()
        feeder.publish_message()
        thread._channel.basic_publish.assert_called()

        # Check if it was increased:
        self.assertEqual(feeder._RabbitFeeder__delivery_number, 4)

        # Run code to be tested:
        feeder.reset_delivery_number()

        # Check if it was reset:
        self.assertEqual(feeder._RabbitFeeder__delivery_number, 1)

