###############################################################################
#
# (C) Copyright 2023 EVERYSK TECHNOLOGIES
#
# This is an unpublished work containing confidential and proprietary
# information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
#
###############################################################################

###############################################################################
#   Imports
###############################################################################
from unittest import TestCase, mock

from everysk.core.exceptions import RequiredError
from everysk.core.datetime.datetime import DateTime, timezone, ZoneInfo

from everysk.sdk.entities.query import Query
from everysk.sdk.entities.base import BaseEntity
from everysk.sdk.entities.portfolio.base import Portfolio
from everysk.sdk.entities.portfolio.securities import Securities

from everysk.sdk.base import _mount_self_obj

###############################################################################
#   Base Entity TestCase Implementation
###############################################################################
class TestBaseEntity(TestCase):

    def setUp(self):
        self.expected_response = {
            'filters': [],
            'order': [],
            'projection': None,
            'limit': None,
            'offset': None,
            'page_size': None,
            'page_token': None,
            'distinct_on': []
        }
        self.sample_portfolio_data = {
            'id': 'port_1234567891011211234567890',
            'workspace': 'SampleWorkspace',
            'name': 'SamplePortfolio',
            'tags': ['tag1', 'tag2'],
            'link_uid': 'ABC',
            'description': 'Description',
            'nlv': 1000.0,
            'base_currency': 'USD',
            'date': DateTime.fromisoformat('20231122'),
            'securities': Securities([{'symbol': 'AAPL'}]),
            'version': '1.0',
            'created_on': DateTime.now(),
            'updated_on': DateTime.now(),
            'level': 'HIGH',
            'outstanding_shares': 1000,
            'source_hash': 'XYZ',
            'status': 'OK',
            'portfolio_uid': 'UID123',
            'check_securities': False
        }
        self.portfolio = Portfolio(**self.sample_portfolio_data)

    def test_entity_to_query(self):
        _query = self.portfolio._entity_to_query() # pylint: disable=protected-access
        self.assertDictEqual(_query,
        {
            'filters': [('workspace', '=', 'SampleWorkspace'),
            ('link_uid', '=',
            'ABC'),
                ('name', '<', 'sampleportfolip'),
                ('name', '>=', 'sampleportfolio'),
                ('date', '=', DateTime(2023, 11, 22, 0, 0, tzinfo=ZoneInfo(key='UTC'))),
                ('tags', '=', 'tag1'),
                ('tags', '=', 'tag2')],
            'order': [],
            'projection': None,
            'limit': None,
            'offset': None,
            'page_size': None,
            'page_token': None,
            'distinct_on': []
        })

    def test_get_id_prefix(self):
        with self.assertRaises(NotImplementedError):
            BaseEntity.get_id_prefix()

    def test_generate_id(self):
        with mock.patch.object(BaseEntity, 'get_id_prefix', return_value='P'), \
                mock.patch('everysk.sdk.entities.base.generate_random_id', return_value='12345'):
            self.assertEqual(BaseEntity.generate_id(), 'P12345')

    def test_validate_id(self):
        with mock.patch.object(BaseEntity, '__init__', side_effect=Exception("Mocked Exception")):
            self.assertFalse(BaseEntity.validate_id('invalid_id'))

        with mock.patch.object(BaseEntity, '__init__', return_value=None):
            self.assertTrue(BaseEntity.validate_id('valid_id'))

        with mock.patch.object(BaseEntity, '__init__', return_value=None):
            self.assertFalse(BaseEntity.validate_id(''))

        with mock.patch.object(BaseEntity, '__init__', return_value=None):
            self.assertFalse(BaseEntity.validate_id(None))

    def test_validate(self):
        self.assertTrue(BaseEntity(id="my_id", workspace="my_workspace", name="my_name", created_on=DateTime.now(), updated_on=DateTime.now()).validate())

    def test_raises_validate(self):
        base_entity = BaseEntity()
        self.assertRaisesRegex(
            RequiredError,
            'The created_on attribute is required.',
            base_entity.validate
        )

    def test_get_query(self):
        """ Test _get_query method """
        expected_response = self.expected_response.copy()
        expected_response['projection'] = ['securities']
        result = Portfolio.query.set_projection('securities') # pylint: disable=protected-access
        self.assertDictEqual(result, expected_response)

    def test_query(self):
        """ Test query method """
        expected_response = self.expected_response.copy()
        expected_response['projection'] = ['securities']
        result = Portfolio.query.set_projection('securities')
        self.assertDictEqual(result, expected_response)

    def test_where(self):
        """ Test where method """
        expected_response = self.expected_response.copy()
        expected_response['filters'] = [('date', '=', DateTime(2023, 8, 10, 12, 0, tzinfo=timezone.utc))]
        result = Portfolio.query.where('date', '=', '2023-08-10')
        self.assertDictEqual(result, expected_response)

    def test_sort_by(self):
        """ Test sort_by method """
        expected_response = self.expected_response.copy()
        expected_response['order'] = ['date']
        result = Portfolio.query.sort_by('date')
        self.assertDictEqual(result, expected_response)

    def test_set_projection(self):
        """ Test set_projection method """
        expected_response = self.expected_response.copy()
        expected_response['projection'] = ['securities']
        result = Portfolio.query.set_projection('securities')
        self.assertDictEqual(result, expected_response)

    def test_set_limit(self):
        """ Test set_limit method """
        expected_response = self.expected_response.copy()
        expected_response['limit'] = 5
        result = Portfolio.query.set_limit(5)
        self.assertDictEqual(result, expected_response)

    def test_set_offset(self):
        """ Test set_offset method """
        expected_response = self.expected_response.copy()
        expected_response['offset'] = 10
        result = Portfolio.query.set_offset(10)
        self.assertDictEqual(result, expected_response)

    def test_set_page_size(self):
        """ Test set_page_size method """
        expected_response = self.expected_response.copy()
        expected_response['page_size'] = 15
        result = Portfolio.query.set_page_size(15)
        self.assertDictEqual(result, expected_response)

    def test_set_page_token(self):
        """ Test set_page_token method """
        expected_response = self.expected_response.copy()
        expected_response['page_token'] = 'my_test_page'
        result = Portfolio.query.set_page_token('my_test_page')
        self.assertDictEqual(result, expected_response)

    def test_load(self):
        """ Test find method """
        expected_response = Query(Portfolio)
        expected_response.update(self.expected_response)
        expected_response['filters'] = [('workspace', '=', 'my_workspace')]

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            result = Portfolio.query.where('workspace', 'my_workspace').load(offset=5)

        mock_http_connection.assert_any_call(class_name='Query', params={'offset': 5}, method_name='load', self_obj=_mount_self_obj(expected_response))
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsInstance(result, Portfolio)

    def test_list(self):
        """ Test list method """
        expected_response = Query(Portfolio)
        expected_response.update(self.expected_response)
        expected_response['filters'] = [('workspace', '=', 'my_workspace')]

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            Portfolio.query.where('workspace', 'my_workspace').loads(limit=10, offset=5)

        mock_http_connection.assert_any_call(class_name='Query', params={'limit': 10, 'offset': 5}, method_name='loads', self_obj=_mount_self_obj(expected_response))
        mock_http_connection.return_value.get_response.assert_called_once_with()

    def test_page(self):
        """ Test page method """
        expected_response = Query(Portfolio)
        expected_response.update(self.expected_response)
        expected_response['filters'] = [('workspace', '=', 'my_workspace')]

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            Portfolio.query.where('workspace', 'my_workspace').page(page_size=10, page_token='page_token')

        mock_http_connection.assert_any_call(class_name='Query', params={'page_size': 10, 'page_token': 'page_token'}, method_name='page', self_obj=_mount_self_obj(expected_response))
        mock_http_connection.return_value.get_response.assert_called_once_with()

    def test_pages(self):
        """ Test pages method """
        expected_response = Query(Portfolio)
        expected_response.update(self.expected_response)
        expected_response['filters'] = [('workspace', '=', 'my_workspace')]

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            result = Portfolio.query.where('workspace', 'my_workspace').pages(page_size=10)
            next(result)

        mock_http_connection.assert_any_call(class_name='Query', params={'page_size': 10, 'page_token': Undefined}, method_name='page', self_obj=_mount_self_obj(expected_response))
        mock_http_connection.return_value.get_response.assert_called_once_with()

    def test_script_query(self):
        """ Test script_query method """
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            Portfolio.script.fetch(user_input='port_12456', variant='select', workspace=None)

        mock_http_connection.assert_any_call(self_obj={'_klass': 'Portfolio'}, params={'user_input': 'port_12456', 'variant': 'select', 'workspace': None}, class_name='Script', method_name='fetch')
        mock_http_connection.return_value.get_response.assert_called_once_with()

    def test_retrieve(self):
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = self.sample_portfolio_data
            result = Portfolio.retrieve(entity_id=self.sample_portfolio_data['id'])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id']}, method_name='retrieve', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertEqual(result, self.portfolio)
        self.assertIsInstance(result, Portfolio)

    def test_retrieve_with_none(self):
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = None
            result = Portfolio.retrieve(entity_id=self.sample_portfolio_data['id'])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id']}, method_name='retrieve', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsNone(result)

    def test_create(self):
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = self.sample_portfolio_data
            result = Portfolio.create(entity_dict=self.sample_portfolio_data)

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_dict': self.sample_portfolio_data}, method_name='create', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertEqual(result, self.portfolio)
        self.assertIsInstance(result, Portfolio)

    def test_modify(self):
        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['base_currency'] = 'BRL'
        modified_portfolio = Portfolio(**sample_portfolio_data)
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = sample_portfolio_data
            result = Portfolio.modify(entity_id=self.sample_portfolio_data['id'], overwrites={'base_currency': 'BRL'})

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id'], 'overwrites': {'base_currency': 'BRL'}}, method_name='modify', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertEqual(result, modified_portfolio)
        self.assertIsInstance(result, Portfolio)

    def test_modify_return_none(self):
        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['base_currency'] = 'BRL'
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = None
            result = Portfolio.modify(entity_id=self.sample_portfolio_data['id'], overwrites={'base_currency': 'BRL'})

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id'], 'overwrites': {'base_currency': 'BRL'}}, method_name='modify', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsNone(result)

    def test_remove(self):
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = self.sample_portfolio_data
            result = Portfolio.remove(entity_id=self.sample_portfolio_data['id'])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id']}, method_name='remove', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertEqual(result, self.portfolio)
        self.assertIsInstance(result, Portfolio)

    def test_remove_return_none(self):
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = None
            result = Portfolio.remove(entity_id=self.sample_portfolio_data['id'])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id']}, method_name='remove', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsNone(result)

    def test_clone(self):
        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['base_currency'] = 'BRL'
        modified_portfolio = Portfolio(**sample_portfolio_data)
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = sample_portfolio_data
            result = Portfolio.clone(entity_id=self.sample_portfolio_data['id'], overwrites={'base_currency': 'BRL'})

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id'], 'overwrites': {'base_currency': 'BRL'}}, method_name='clone', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertEqual(result, modified_portfolio)
        self.assertIsInstance(result, Portfolio)

    def test_clone_return_none(self):
        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['base_currency'] = 'BRL'
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = None
            result = Portfolio.clone(entity_id=self.sample_portfolio_data['id'], overwrites={'base_currency': 'BRL'})

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id': self.sample_portfolio_data['id'], 'overwrites': {'base_currency': 'BRL'}}, method_name='clone', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsNone(result)

    def test_create_many(self):
        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['base_currency'] = 'BRL'
        sample_portfolio_data['check_securities'] = True

        res_modified_portfolio = sample_portfolio_data.copy()
        res_modified_portfolio['check_securities'] = False

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = [self.sample_portfolio_data, res_modified_portfolio]
            result = Portfolio.create_many(entity_dict_list=[self.sample_portfolio_data, sample_portfolio_data])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_dict_list': [self.sample_portfolio_data, sample_portfolio_data]}, method_name='create_many', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsInstance(result, list)
        self.assertEqual(result[0], self.portfolio)
        self.assertEqual(result[1], res_modified_portfolio)
        self.assertIsInstance(result[0], Portfolio)
        self.assertIsInstance(result[1], Portfolio)

    def test_modify_many(self):
        original_portfolio_data = self.sample_portfolio_data.copy()
        original_portfolio_data['name'] = 'modified_1'
        original_portfolio = Portfolio(**original_portfolio_data)

        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['id'] = 'port_1234567891011211234567891'
        sample_portfolio_data['name'] = 'modified_2'
        modified_portfolio = Portfolio(**sample_portfolio_data)

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = [original_portfolio_data, sample_portfolio_data]
            result = Portfolio.modify_many(entity_id_list=[self.sample_portfolio_data['id'], sample_portfolio_data['id']], overwrites=[{'name': 'modified_1'}, {'name': 'modified_2'}])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id_list': [self.sample_portfolio_data['id'], modified_portfolio['id']], 'overwrites': [{'name': 'modified_1'}, {'name': 'modified_2'}]}, method_name='modify_many', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsInstance(result, list)
        self.assertEqual(result[0], original_portfolio)
        self.assertEqual(result[1], modified_portfolio)
        self.assertIsInstance(result[0], Portfolio)
        self.assertIsInstance(result[1], Portfolio)

    def test_remove_many(self):
        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['id'] = 'port_1234567891011211234567891'
        sample_portfolio_data['base_currency'] = 'BRL'

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = [self.sample_portfolio_data['id'], sample_portfolio_data['id']]
            result = Portfolio.remove_many(entity_id_list=[self.sample_portfolio_data['id'], sample_portfolio_data['id']])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id_list': [self.sample_portfolio_data['id'], sample_portfolio_data['id']]}, method_name='remove_many', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertListEqual(result, mock_http_connection.return_value.get_response.return_value)

    def test_clone_many(self):
        original_portfolio_data = self.sample_portfolio_data.copy()
        original_portfolio_data['name'] = 'modified_1'
        original_portfolio_data['workspace'] = 'modified_1'
        original_portfolio = Portfolio(**original_portfolio_data)

        sample_portfolio_data = self.sample_portfolio_data.copy()
        sample_portfolio_data['id'] = 'port_1234567891011211234567891'
        sample_portfolio_data['name'] = 'modified_2'
        sample_portfolio_data['workspace'] = 'modified_2'
        modified_portfolio = Portfolio(**sample_portfolio_data)

        entity_id_list = [original_portfolio['id'], sample_portfolio_data['id']]

        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = [original_portfolio_data, sample_portfolio_data]
            result = Portfolio.clone_many(entity_id_list=entity_id_list, overwrites=[{'name': 'modified_1'}, {'name': 'modified_1'}])

        mock_http_connection.assert_any_call(class_name='Portfolio', params={'entity_id_list': entity_id_list, 'overwrites': [{'name': 'modified_1'}, {'name': 'modified_1'}]}, method_name='clone_many', self_obj=None)
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsInstance(result, list)
        self.assertEqual(result[0], original_portfolio)
        self.assertEqual(result[1], modified_portfolio)
        self.assertIsInstance(result[0], Portfolio)
        self.assertIsInstance(result[1], Portfolio)

    def test_save(self):
        portfolio = Portfolio(**self.sample_portfolio_data)
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = self.sample_portfolio_data
            result = portfolio.save()

        mock_http_connection.assert_any_call(class_name='Portfolio', params={}, method_name='save', self_obj=_mount_self_obj(portfolio))
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertEqual(result, self.portfolio)
        self.assertIsInstance(result, Portfolio)

    def test_delete(self):
        portfolio = Portfolio(**self.sample_portfolio_data)
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = self.sample_portfolio_data
            result = portfolio.delete()

        mock_http_connection.assert_any_call(class_name='Portfolio', params={}, method_name='delete', self_obj=_mount_self_obj(portfolio))
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertEqual(result, self.portfolio)
        self.assertIsInstance(result, Portfolio)

    def test_delete_return_none(self):
        portfolio = Portfolio(**self.sample_portfolio_data)
        with mock.patch('everysk.sdk.base.HttpSDKPOSTConnection') as mock_http_connection:
            mock_http_connection.return_value.get_response.return_value = None
            result = portfolio.delete()

        mock_http_connection.assert_any_call(class_name='Portfolio', params={}, method_name='delete', self_obj=_mount_self_obj(portfolio))
        mock_http_connection.return_value.get_response.assert_called_once_with()
        self.assertIsNone(result)
