import gc

from src.backup_crm_csv_data_process import backup_crm_csv_data_process
from src.backup_crm_data_process import backup_crm_data_process
from src.check_object_info_process import check_object_info_process
from src.config.objects import FetchTargetObjects
from src.convert_crm_csv_data_process import convert_crm_csv_data_process
from src.copy_crm_csv_data_process import copy_crm_csv_data_process
from src.error.exceptions import MeDaCaCRMDataFetchException
from src.fetch_crm_data_process import fetch_crm_data_process
from src.prepare_data_fetch_process import prepare_data_fetch_process
from src.set_datetime_period_process import set_datetime_period_process
from src.system_var.constants import OBJECT_NAME_KEY
from src.upload_last_fetch_datetime_process import \
    upload_last_fetch_datetime_process
from src.upload_result_data_process import upload_result_data_process
from src.util.execute_datetime import ExecuteDateTime
from src.util.logger import logger_instance as logger


def controller() -> None:
    """コントロール処理"""

    try:
        # ① CRMデータ取得処理開始ログを出力する
        logger.info('I-CTRL-01 CRMデータ取得処理を開始します')

        # ② データ取得準備処理を呼び出す
        logger.info('I-CTRL-02 データ取得準備処理呼び出し')

        fetch_target_objects, execute_datetime, process_result = prepare_data_fetch_process()

        # ③ object_infoのobjectsキーの値の件数分ループする
        logger.info('I-CTRL-03 取得対象オブジェクトのループ処理開始')

        process_result = _fetch_crm_data(fetch_target_objects, execute_datetime, process_result)

        # ④ すべてのオブジェクトの処理が完了したことと、オブジェクト毎の処理結果をログに出力する
        logger.info(f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果：[{process_result}]')

        # 最終結果が0件(1件も処理されていない)の場合、ログ出力して処理を終了する
        if len(process_result.keys()) == 0:
            logger.info('I-CTRL-21 処理対象のデータが存在しませんでした')
            return 0

        # ⑤ 取得処理実施結果アップロード処理を呼び出す
        logger.info('I-CTRL-18 CRM_取得処理実施結果ファイルアップロード処理開始')
        upload_result_data_process(process_result, execute_datetime)

        # ⑥ 最終結果をチェックし、チェック結果をログに出力
        _check_process_result(process_result)

        return 0

    except MeDaCaCRMDataFetchException as e:
        logger.error(f'E-ERR-01 [{e.func_name}]でエラーが発生したため、処理を終了します')
        logger.exception(f'{e.error_id} {e}')
        raise e

    except Exception as e:
        logger.exception(f'E-ERR-02 予期せぬエラーが発生したため、処理を終了します エラー内容: [{e}]')
        raise e

    finally:
        # ⑦ CRMデータ取得処理終了ログを出力する
        logger.info('I-CTRL-20 CRMデータ取得処理を終了します')


def _fetch_crm_data(fetch_target_objects: FetchTargetObjects, execute_datetime: ExecuteDateTime, process_result: dict):
    """取得対象オブジェクト情報をループし、１オブジェクトごとのデータを取得する

    Args:
        fetch_target_objects (FetchTargetObjects): CRMオブジェクト情報インスタンス
        execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
        process_result (dict): 取得処理実行結果辞書オブジェクト

    Returns:
        process_result: 取得処理実行結果辞書オブジェクト
    """

    for object_info in fetch_target_objects:
        try:
            # 1．処理結果出力用のマップの初期化
            process_result[object_info.get(OBJECT_NAME_KEY)] = 'fail'

            _fetch_crm_data_per_object(object_info, execute_datetime)

            # 16．処理結果出力用のマップの更新
            process_result[object_info.get(OBJECT_NAME_KEY)] = 'success'

        except MeDaCaCRMDataFetchException as e:
            logger.info(f'{e.error_id} {e}')
            logger.info(
                f'I-ERR-03 [{object_info.get(OBJECT_NAME_KEY)}] の[{e.func_name}]でエラーが発生しました 次のオブジェクトの処理に移行します', exc_info=True)
            continue

        except Exception as e:
            logger.info(
                f'I-ERR-04 [{object_info.get(OBJECT_NAME_KEY)}] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [{e}]', exc_info=True)
            continue

    return process_result


def _fetch_crm_data_per_object(object_info: dict, execute_datetime: ExecuteDateTime) -> None:
    """オブジェクトごとにCRMのデータを取得し、取込フォルダにアップロードする

    Args:
        object_info (dict): 取得対象オブジェクト情報
        execute_datetime (ExecuteDateTime): 実行日時取得インスタンス
    """

    # 2. 対象オブジェクト情報の内容をログに出力する
    logger.debug(f'D-CTRL-04 対象のオブジェクト情報を出力します　オブジェクト情報：[{object_info}]')

    # 3. オブジェクト情報形式チェック処理を呼び出す
    logger.info('I-CTRL-05 オブジェクト情報形式チェック処理呼び出し')

    target_object = check_object_info_process(object_info, execute_datetime)
    target_object_name = target_object.object_name

    # 4. 処理対象のオブジェクト名をログ出力する
    logger.info(
        f'I-CTRL-06 [{target_object_name}]のデータ取得を開始します')

    # 5. オブジェクト情報.is_skipがTrueの場合、次のオブジェクトの処理に移行する
    if target_object.is_skip is True:
        logger.info(
            f'I-CTRL-07 [{target_object_name}]のデータ取得処理をスキップします')
        return

    # 6. データ取得期間設定処理を呼び出す
    logger.info(
        f'I-CTRL-08 [{target_object_name}]のデータ取得期間設定処理呼び出し')

    last_fetch_datetime = set_datetime_period_process(target_object, execute_datetime)

    # 7. CRMデータ取得処理を呼び出す
    logger.info(
        f'I-CTRL-09 [{target_object_name}]のデータ取得処理呼び出し')

    crm_data_response = fetch_crm_data_process(target_object, last_fetch_datetime)

    # 取得件数が0件の場合、次のオブジェクトの処理に移行する
    if len(crm_data_response) == 0:
        logger.info(
            f'I-CTRL-22 [{target_object_name}]のレコード件数が0件のため、ファイルアップロードをスキップします')
        return

    # 8. 出力ファイル名をログ出力する
    logger.info(
        f'I-CTRL-10 [{target_object_name}] の出力ファイル名は [{target_object.upload_file_name}] となります')

    # 9. CRM電文データバックアップ処理を呼び出す
    logger.info(
        f'I-CTRL-11 [{target_object_name}] CRM電文データバックアップ処理呼び出し')
    backup_crm_data_process(target_object, crm_data_response, execute_datetime)

    # 10. CSV変換処理を呼び出す
    logger.info(
        f'I-CTRL-12 [{target_object.object_name}] CSV変換処理呼び出し')
    csv_string = convert_crm_csv_data_process(target_object, crm_data_response)

    # 11. CSVバックアップ処理を呼び出す
    logger.info(
        f'I-CTRL-13 [{target_object_name}] CSVデータバックアップ処理呼び出し')
    backup_crm_csv_data_process(target_object, execute_datetime, csv_string)

    # 12. CSVアップロード処理を呼び出す
    logger.info(
        f'I-CTRL-14 [{target_object_name}] CSVデータアップロード処理呼び出し')
    copy_crm_csv_data_process(target_object, execute_datetime)

    # 13. メモリ解放
    del crm_data_response
    del csv_string
    gc.collect()

    # 14. 前回取得日時ファイル更新処理を呼びだす
    logger.info(
        f'I-CTRL-15 [{target_object_name}] 前回取得日時ファイル更新処理呼び出し')
    upload_last_fetch_datetime_process(target_object, last_fetch_datetime)

    # 15. 1オブジェクトのアップロードが完了した旨をログに出力する
    logger.info(f'I-CTRL-16 [{target_object_name}] 処理正常終了')

    return


def _check_process_result(process_result: dict) -> None:
    """取得処理結果がすべて成功か、一部失敗しているかを判定し、ログ出力する

    Args:
        process_result (dict): 取得処理結果辞書オブジェクト
    """
    if not all([v == 'success' for v in process_result.values()]):
        logger.error('E-CTRL-01 一部のデータ取得に失敗しています  詳細はログをご確認ください')
        return

    logger.info('I-CTRL-19 すべてのデータの取得に成功しました')

    return
