"""
！！！！注意！！！！
当テストコードはSalesforceのレコードに依存しています。
Accountオブジェクトの下記SFIDのレコードはいじらないようにしてください
    - 0015i00000LNywwAAD(テスト取引先1)
    - 0015i00000LOClSAAX(テスト取引先2)
    - 0015i00000LOCmGAAX(テスト取引先3)

変更してしまった場合は各SOQLの取得日付とデータを修正してください
"""

from typing import OrderedDict

import pytest
from requests.exceptions import ConnectTimeout, ReadTimeout
from src.config.objects import LastFetchDatetime, TargetObject
from src.salesforce.salesforce_api import SalesforceApiClient
from src.salesforce.soql_builder import SOQLBuilder
from src.util.execute_datetime import ExecuteDateTime


class TestSalesforceApiClient:

    def test_fetch_sf_count(self):
        """
        Cases:
            Salesforceからオブジェクトの件数が取得できること
        Arranges:
            SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
        Expects:
            取得件数が1件以上になる
        """
        soql = """SELECT
            COUNT(Id)
        FROM
            Account
        WHERE
            SystemModstamp > 2022-08-23T01:56:39.000Z AND
            SystemModstamp <= 2022-08-24T00:00:00.000Z
        """
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_count(soql)
        assert actual >= 1

    def test_fetch_sf_count_zero_record(self):
        """
        Cases:
            取得範囲外の場合、Salesforceからオブジェクトの件数が取得できないこと
        Arranges:
            SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
        Expects:
            取得件数が0件になる
        """
        soql = """SELECT
            COUNT(Id)
        FROM
            Account
        WHERE
            SystemModstamp > 1999-01-01T00:00:00.000Z AND
            SystemModstamp <= 2000-01-01T00:00:00.000Z
        """
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_count(soql)
        assert actual >= 0

    def test_fetch_sf_count_by_soql_builder_system_modstamp_to_ge(self):
        """
        Cases:
            - SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトの件数が取得できること
            - SystemModStampがFrom指定日付未満のレコードは取得できないこと
            - SystemModStampのToが指定日付以上のレコードは取得できること
        Arranges:
            - SalesforceのAccountオブジェクトに、レコードを作成する
            - LastFetchDatetimeのFromがSystemModstamp未満になるように指定する（UTC指定）
            - LastFetchDatetimeのToがSystemModstampピッタリになるように指定する（UTC指定）
        Expects:
            取得件数が1になる
        """

        execute_datetime = ExecuteDateTime()
        last_fetch_datetime = LastFetchDatetime({
            'last_fetch_datetime_from': '2022-08-23T02:38:59.000Z',
            'last_fetch_datetime_to': '2022-08-23T02:39:00.000Z',
        }, execute_datetime)
        target_object = TargetObject({
            'object_name': 'Account',
            'columns': [
                'Id',
                'Name',
                'SystemModstamp',
                'LastModifiedDate',
                'CustomItem1__c',
                'CustomItem2__c',
                'CustomItem3__c',
                'CustomItem4__c',
                'CustomItem5__c',
                'CustomItem6__c',
                'CustomItem7__c',
                'CustomItem8__c'
            ]
        }, execute_datetime)
        soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
        soql = soql_builder.create_count_soql()
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_count(soql)
        assert actual == 1

    def test_fetch_sf_count_by_soql_builder_system_modstamp_to_lt(self):
        """
        Cases:
            - SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトの件数が取得できること
            - SystemModStampのFromが指定日付のより大きいレコードは取得できること
            - SystemModStampのToが指定日付未満のレコードは取得できないこと
        Arranges:
            - SalesforceのAccountオブジェクトに、レコードを作成する
            - LastFetchDatetimeのFromがSystemModstampより大きくなるように指定する（UTC指定）
            - LastFetchDatetimeのToがSystemModstamp未満になるように指定する（UTC指定）
        Expects:
            取得件数が1になる
        """

        execute_datetime = ExecuteDateTime()
        last_fetch_datetime = LastFetchDatetime({
            'last_fetch_datetime_from': '2022-08-23T02:38:00.000Z',
            'last_fetch_datetime_to': '2022-08-23T02:39:01.000Z',
        }, execute_datetime)
        target_object = TargetObject({
            'object_name': 'Account',
            'columns': [
                'Id',
                'Name',
                'SystemModstamp',
                'LastModifiedDate',
                'CustomItem1__c',
                'CustomItem2__c',
                'CustomItem3__c',
                'CustomItem4__c',
                'CustomItem5__c',
                'CustomItem6__c',
                'CustomItem7__c',
                'CustomItem8__c'
            ]
        }, execute_datetime)
        soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
        soql = soql_builder.create_count_soql()
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_count(soql)
        assert actual == 1

    def test_fetch_sf_count_by_soql_builder_system_modstamp_all_range(self):
        """
        Cases:
            - SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトの件数が取得できること
            - SystemModStampのFromが2000年1月1日以降のレコードが取得できること
            - SystemModStampのToが2100年12月31日未満のレコードが取得できること
        Arranges:
            - SalesforceのAccountオブジェクトに、レコードを作成する
            - LastFetchDatetimeのFromに2000年1月1日を指定する
            - LastFetchDatetimeのToに2100年12月31日を指定する
        Expects:
            取得件数が17になる
        """

        execute_datetime = ExecuteDateTime()
        last_fetch_datetime = LastFetchDatetime({
            'last_fetch_datetime_from': '2000-01-01T00:00:00.000Z',
            'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
        }, execute_datetime)
        target_object = TargetObject({
            'object_name': 'Account',
            'columns': [
                'Id',
                'Name',
                'SystemModstamp',
                'LastModifiedDate',
                'CustomItem1__c',
                'CustomItem2__c',
                'CustomItem3__c',
                'CustomItem4__c',
                'CustomItem5__c',
                'CustomItem6__c',
                'CustomItem7__c',
                'CustomItem8__c'
            ]
        }, execute_datetime)
        soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
        soql = soql_builder.create_count_soql()
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_count(soql)
        assert actual == 17

    def test_fetch_sf_data_one_record(self):
        """
        Cases:
            Salesforceからオブジェクトが取得できること
        Arranges:
            SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
        Expects:
            オブジェクトが取得でき、期待値と一致していること
        """
        soql = """SELECT
            Id,
            Name,
            SystemModstamp,
            LastModifiedDate,
            CustomItem1__c,
            CustomItem2__c,
            CustomItem3__c,
            CustomItem4__c,
            CustomItem5__c,
            CustomItem6__c,
            CustomItem7__c,
            CustomItem8__c
        FROM
            Account
        WHERE
            SystemModstamp > 2022-08-23T01:56:39.000Z AND
            SystemModstamp <= 2022-08-24T00:00:00.000Z
        """
        sut = SalesforceApiClient()

        expect = {
            'Name': 'テスト取引先名1',
            'CustomItem1__c': 'テスト',
            'CustomItem2__c': 1.0,
            'CustomItem3__c': True,
            'CustomItem4__c': '01:15:00.000Z',
            'CustomItem5__c': '1;2;3;4',
            'CustomItem6__c': '改行ありの\r\nテスト\r\n項目です',
            'CustomItem7__c': '2022-08-04',
            'CustomItem8__c': '2022-08-04T03:00:00.000+0000',
        }
        actual = sut.fetch_sf_data(soql)
        assert len(actual) >= 1
        # 複数取れるが、アサーション対象は1つだけ
        actual = [record for record in actual if record['Name'] == 'テスト取引先名1']
        # Id, SystemModstamp, LastModifiedDateは自動生成なので、キーの有無だけ確認する
        # Attributesも
        assert 'Id' in actual[0].keys()
        assert 'SystemModstamp' in actual[0].keys()
        assert 'LastModifiedDate' in actual[0].keys()
        assert 'attributes' in actual[0].keys()

        del actual[0]['Id']
        del actual[0]['SystemModstamp']
        del actual[0]['LastModifiedDate']
        del actual[0]['attributes']

        assert dict(actual[0]) == expect

    def test_fetch_sf_data_zero_record(self):
        """
        Cases:
            取得範囲外の場合、Salesforceからオブジェクトが取得できないこと
        Arranges:
            SalesforceのAccountオブジェクトに、レコードを作成する(手作業、コード上では行わない)
        Expects:
            取得件数が0件になる
        """
        soql = """SELECT
            Id,
            Name,
            SystemModstamp,
            LastModifiedDate,
            CustomItem1__c,
            CustomItem2__c,
            CustomItem3__c,
            CustomItem4__c,
            CustomItem5__c,
            CustomItem6__c,
            CustomItem7__c,
            CustomItem8__c
        FROM
            Account
        WHERE
            SystemModstamp > 1999-01-01T00:00:00.000Z AND
            SystemModstamp <= 2000-01-01T00:00:00.000Z
        """
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_data(soql)
        assert len(actual) >= 0

    def test_fetch_sf_data_by_soql_builder_system_modstamp_to_ge(self):
        """
        Cases:
            - SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
            - SystemModStampがFrom指定日付未満のレコードは取得できないこと
            - SystemModStampのToが指定日付以上のレコードは取得できること
        Arranges:
            - SalesforceのAccountオブジェクトに、レコードを作成する
            - LastFetchDatetimeのFromがSystemModstamp未満になるように指定する（UTC指定）
            - LastFetchDatetimeのToがSystemModstampピッタリになるように指定する（UTC指定）
        Expects:
            取得できたオブジェクト1件が期待値どおりであること
        """

        execute_datetime = ExecuteDateTime()
        last_fetch_datetime = LastFetchDatetime({
            'last_fetch_datetime_from': '2022-08-23T03:48:39.000Z',
            'last_fetch_datetime_to': '2022-08-23T03:48:40.000Z',
        }, execute_datetime)
        target_object = TargetObject({
            'object_name': 'Account',
            'columns': [
                'Id',
                'Name',
                'SystemModstamp',
                'LastModifiedDate',
                'CustomItem1__c',
                'CustomItem2__c',
                'CustomItem3__c',
                'CustomItem4__c',
                'CustomItem5__c',
                'CustomItem6__c',
                'CustomItem7__c',
                'CustomItem8__c'
            ]
        }, execute_datetime)
        soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
        soql = soql_builder.create_fetch_soql()
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_data(soql)
        assert len(actual) == 1

        # Id, SystemModstamp, LastModifiedDateは自動生成なので、キーの有無だけ確認する
        # Attributesも
        assert 'Id' in actual[0].keys()
        assert 'SystemModstamp' in actual[0].keys()
        assert 'LastModifiedDate' in actual[0].keys()
        assert 'attributes' in actual[0].keys()

        del actual[0]['Id']
        del actual[0]['SystemModstamp']
        del actual[0]['LastModifiedDate']
        del actual[0]['attributes']

        expect = {
            'Name': 'テスト取引先名3',
            'CustomItem1__c': 'テスト3',
            'CustomItem2__c': 3.0,
            'CustomItem3__c': True,
            'CustomItem4__c': '00:45:00.000Z',
            'CustomItem5__c': '2;3',
            'CustomItem6__c': 'かいぎょ',
            'CustomItem7__c': '2022-08-06',
            'CustomItem8__c': '2022-08-06T00:00:00.000+0000',
        }

        assert dict(actual[0]) == expect

    def test_fetch_sf_data_by_soql_builder_system_modstamp_to_gt(self):
        """
        Cases:
            - SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
            - SystemModStampのFromが指定日付のより大きいレコードは取得できること
            - SystemModStampのToが指定日付未満のレコードは取得できないこと
        Arranges:
            - SalesforceのAccountオブジェクトに、レコードを作成する
            - LastFetchDatetimeのFromがSystemModstampより小さくなるように指定する（UTC指定）
            - LastFetchDatetimeのToがSystemModstampより大きくなるように指定する（UTC指定）
        Expects:
            取得できたオブジェクト1件が期待値どおりであること
        """

        execute_datetime = ExecuteDateTime()
        last_fetch_datetime = LastFetchDatetime({
            'last_fetch_datetime_from': '2022-08-23T03:42:24.000Z',
            'last_fetch_datetime_to': '2022-08-23T03:42:26.000Z',
        }, execute_datetime)
        target_object = TargetObject({
            'object_name': 'Account',
            'columns': [
                'Id',
                'Name',
                'SystemModstamp',
                'LastModifiedDate',
                'CustomItem1__c',
                'CustomItem2__c',
                'CustomItem3__c',
                'CustomItem4__c',
                'CustomItem5__c',
                'CustomItem6__c',
                'CustomItem7__c',
                'CustomItem8__c'
            ]
        }, execute_datetime)
        soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
        soql = soql_builder.create_fetch_soql()
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_data(soql)
        assert len(actual) == 1

        # Id, SystemModstamp, LastModifiedDateは自動生成なので、キーの有無だけ確認する
        # Attributesも
        assert 'Id' in actual[0].keys()
        assert 'SystemModstamp' in actual[0].keys()
        assert 'LastModifiedDate' in actual[0].keys()
        assert 'attributes' in actual[0].keys()

        del actual[0]['Id']
        del actual[0]['SystemModstamp']
        del actual[0]['LastModifiedDate']
        del actual[0]['attributes']

        expect = {
            'Name': 'テスト取引先名2',
            'CustomItem1__c': 'テスト2',
            'CustomItem2__c': 2.0,
            'CustomItem3__c': False,
            'CustomItem4__c': '00:30:00.000Z',
            'CustomItem5__c': '1;4',
            'CustomItem6__c': '改行あり項目',
            'CustomItem7__c': '2022-08-05',
            'CustomItem8__c': '2022-08-04T23:30:00.000+0000',
        }

        assert dict(actual[0]) == expect

    def test_fetch_sf_data_by_soql_builder_address_item_check(self):
        """
        Cases:
            - SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
            - できること
        Arranges:
            - SalesforceのAccountオブジェクトに、住所項目を持つレコードを作成する
            - 住所項目を持つレコードだけが取れるよう日付を設定する
        Expects:
            取得できたオブジェクト件数が1件になる
            住所項目(BillingAddress)が想定通りの値になっていること
        """

        execute_datetime = ExecuteDateTime()
        last_fetch_datetime = LastFetchDatetime({
            'last_fetch_datetime_from': '2022-08-23T02:38:00.000Z',
            'last_fetch_datetime_to': '2022-08-23T02:39:00.000Z',
        }, execute_datetime)
        target_object = TargetObject({
            'object_name': 'Account',
            'columns': [
                'Id',
                'Name',
                'SystemModstamp',
                'LastModifiedDate',
                'BillingStreet',
                'BillingCity',
                'BillingState',
                'BillingPostalCode',
                'BillingCountry',
                'BillingLatitude',
                'BillingLongitude',
                'BillingGeocodeAccuracy',
                'BillingAddress',
            ]
        }, execute_datetime)
        soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
        soql = soql_builder.create_fetch_soql()
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_data(soql)
        assert len(actual) == 1
        expect_address = OrderedDict([
            ("city", '〇〇区'),
            ("country", "日本"),
            ("geocodeAccuracy", None),
            ("latitude", None),
            ("longitude", None),
            ("postalCode", '999-9999'),
            ("state", '東京都'),
            ("street", '△△-✗✗'),
        ])

        assert actual[0]['BillingAddress'] == expect_address
        assert actual[0]['BillingCity'] == '〇〇区'
        assert actual[0]['BillingCountry'] == '日本'
        assert actual[0]['BillingGeocodeAccuracy'] is None
        assert actual[0]['BillingLatitude'] is None
        assert actual[0]['BillingLongitude'] is None
        assert actual[0]['BillingPostalCode'] == '999-9999'
        assert actual[0]['BillingState'] == '東京都'
        assert actual[0]['BillingStreet'] == '△△-✗✗'

    def test_fetch_sf_data_by_soql_builder_system_modstamp_all_range(self):
        """
        Cases:
            - SOQLBuilderから生成したSOQLで、Salesforceからオブジェクトが取得できること
            - SystemModStampのFromが2000年1月1日以降のレコードが取得できること
            - SystemModStampのToが2100年12月31日未満のレコードが取得できること
        Arranges:
            - SalesforceのAccountオブジェクトに、レコードを作成する
            - LastFetchDatetimeのFromに2000年1月1日を指定する
            - LastFetchDatetimeのToに2100年12月31日を指定する
        Expects:
            取得できたオブジェクト件数が17件になる
        """

        execute_datetime = ExecuteDateTime()
        last_fetch_datetime = LastFetchDatetime({
            'last_fetch_datetime_from': '2000-01-01T00:00:00.000Z',
            'last_fetch_datetime_to': '2100-12-31T23:59:59.000Z',
        }, execute_datetime)
        target_object = TargetObject({
            'object_name': 'Account',
            'columns': [
                'Id',
                'Name',
                'SystemModstamp',
                'LastModifiedDate',
                'BillingAddress',
                'CustomItem1__c',
                'CustomItem2__c',
                'CustomItem3__c',
                'CustomItem4__c',
                'CustomItem5__c',
                'CustomItem6__c',
                'CustomItem7__c',
                'CustomItem8__c'
            ]
        }, execute_datetime)
        soql_builder = SOQLBuilder(target_object, last_fetch_datetime)
        soql = soql_builder.create_fetch_soql()
        sut = SalesforceApiClient()

        actual = sut.fetch_sf_data(soql)
        assert len(actual) == 17
        # 内容の確認は別のケースで行っているため省略

    def test_raise_create_instance_cause_auth_failed(self, monkeypatch):
        """
        Cases:
            存在しないユーザを指定した場合、エラーが発生すること
        Arranges:
            CRMのユーザ名を保持する環境変数に、存在しないユーザー名を指定する
        Expects:
            ユーザ認証でエラーが発生すること
        """
        monkeypatch.setattr('src.salesforce.salesforce_api.CRM_USER_NAME', 'invalid_username')
        with pytest.raises(Exception):
            SalesforceApiClient()

    def test_raise_fetch_sf_count_auth_timeout(self, monkeypatch):
        """
        Cases:
            認証タイムアウトが発生した場合、エラーが発生すること
        Arranges:
            認証タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
        Expects:
            コネクションタイムアウトエラーが発生すること
        """
        monkeypatch.setattr('src.salesforce.salesforce_api.CRM_AUTH_TIMEOUT', 0.0000000001)
        sf = SalesforceApiClient()
        soql = """SELECT
            COUNT(Id)
        FROM
            Account
        WHERE
            SystemModstamp > 2022-08-04T00:00:00.000Z AND
            SystemModstamp <= 2022-08-06T00:00:00.000Z
        """
        with pytest.raises(ConnectTimeout):
            sf.fetch_sf_count(soql)

    def test_raise_fetch_sf_count_read_timeout(self, monkeypatch):
        """
        Cases:
            読み取りタイムアウトが発生した場合、エラーが発生すること
        Arranges:
            CRMの件数取得タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
        Expects:
            読み取りタイムアウトエラーが発生すること
        """
        monkeypatch.setattr('src.salesforce.salesforce_api.CRM_GET_RECORD_COUNT_TIMEOUT', 0.0000000001)
        sf = SalesforceApiClient()
        soql = """SELECT
            COUNT(Id)
        FROM
            Account
        WHERE
            SystemModstamp > 2022-08-04T00:00:00.000Z AND
            SystemModstamp <= 2022-08-06T00:00:00.000Z
        """
        with pytest.raises(ReadTimeout):
            sf.fetch_sf_count(soql)

    def test_raise_fetch_sf_count_invalid_soql(self, monkeypatch):
        """
        Cases:
            不正なSOQLを指定した場合に、エラーが発生すること
        Arranges:
            不正なSOQLを作成する
        Expects:
            エラーが発生すること
        """
        sf = SalesforceApiClient()
        soql = "SELECT"
        with pytest.raises(Exception):
            sf.fetch_sf_count(soql)

    def test_raise_fetch_sf_data_auth_timeout(self, monkeypatch):
        """
        Cases:
            認証タイムアウトが発生した場合、エラーが発生すること
        Arranges:
            認証タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
        Expects:
            コネクションタイムアウトエラーが発生すること
        """
        monkeypatch.setattr('src.salesforce.salesforce_api.CRM_AUTH_TIMEOUT', 0.0000000001)
        sf = SalesforceApiClient()
        soql = """SELECT
            COUNT(Id)
        FROM
            Account
        WHERE
            SystemModstamp > 2022-08-04T00:00:00.000Z AND
            SystemModstamp <= 2022-08-06T00:00:00.000Z
        """
        with pytest.raises(ConnectTimeout):
            sf.fetch_sf_data(soql)

    def test_raise_fetch_sf_data_read_timeout(self, monkeypatch):
        """
        Cases:
            読み取りタイムアウトが発生した場合、エラーが発生すること
        Arranges:
            CRMのデータ取得タイムアウト秒数を保持する環境変数に、0.0000000001を指定する
        Expects:
            読み取りタイムアウトエラーが発生すること
        """
        monkeypatch.setattr('src.salesforce.salesforce_api.CRM_FETCH_RECORD_TIMEOUT', 0.0000000001)
        sf = SalesforceApiClient()
        soql = """SELECT
            COUNT(Id)
        FROM
            Account
        WHERE
            SystemModstamp > 2022-08-04T00:00:00.000Z AND
            SystemModstamp <= 2022-08-06T00:00:00.000Z
        """
        with pytest.raises(ReadTimeout):
            sf.fetch_sf_data(soql)

    def test_raise_fetch_sf_data_invalid_soql(self, monkeypatch):
        """
        Cases:
            不正なSOQLを指定した場合に、エラーが発生すること
        Arranges:
            不正なSOQLを作成する
        Expects:
            エラーが発生すること
        """
        sf = SalesforceApiClient()
        soql = "SELECT"
        with pytest.raises(Exception):
            sf.fetch_sf_data(soql)
