import os
import json
import html
import csv

import pandas as pd

from fastapi import HTTPException
from io import TextIOWrapper
from src.aws.aws_api_client import AWSAPIClient
from src.aws.s3 import S3Client
from src.error.exceptions import DBException
from starlette import status
from datetime import datetime
from src.services.base_service import BaseService
from src.system_var import constants, environment
from src.repositories.base_repository import BaseRepository
from src.repositories.mst_inst_repository import MstInstRepository
from src.repositories.bu_master_cd_repository import BuMasterRepository
from src.repositories.emp_master_repository import EmpMasterRepository
from src.repositories.emp_chg_inst_repository import EmpChgInstRepository
from src.model.internal.master_mainte_csv import MasterMainteCSVItems
from src.model.internal.master_mainte_emp_chg_inst_function import NewEmpChgInstFunction
from src.model.internal.master_mainte_emp_chg_inst_function import ChangeEmpChgInstFunction
from src.model.view.inst_emp_csv_upload_view_model import InstEmpCsvUploadViewModel
from src.model.view.table_override_view_model import TableOverrideViewModel
from src.model.request.master_mainte_csvup import MasterMainteCsvUpModel
from src.model.request.master_mainte_csvdl import MasterMainteCsvDlModel
from src.logging.get_logger import get_logger


logger = get_logger('マスターメンテ')


class MasterMainteService(BaseService):
    REPOSITORIES = {
        'mst_inst_repository': MstInstRepository,
        'emp_master_repository': EmpMasterRepository,
        'bu_master_repository': BuMasterRepository,
        'emp_chginst_repository': EmpChgInstRepository,
    }

    CLIENTS = {
        's3_client': S3Client
    }

    mst_inst_repository: MstInstRepository
    emp_master_repository: EmpMasterRepository
    bu_master_repository: BuMasterRepository
    emp_chginst_repository: EmpChgInstRepository
    s3_client: S3Client

    def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None:
        super().__init__(repositories, clients)
        self.mst_inst_repository = repositories['mst_inst_repository']
        self.emp_master_repository = repositories['emp_master_repository']
        self.bu_master_repository = repositories['bu_master_repository']
        self.emp_chginst_repository = repositories['emp_chginst_repository']
        self.s3_client = clients['s3_client']

    def prepare_mainte_csv_up_view(self,
                                   file: TextIOWrapper,
                                   csv_file_name: str,
                                   csv_upload_form: MasterMainteCsvUpModel) -> InstEmpCsvUploadViewModel:

        if csv_upload_form.select_function != 'new' and csv_upload_form.select_function != 'change':
            raise Exception(f'機能の選択値が不正です: {csv_upload_form.select_function}')
        if csv_upload_form.select_table != 'dummy' and csv_upload_form.select_table != 'real':
            raise Exception(f'登録テーブルの選択値が不正です: {csv_upload_form.select_table}')

        (table_name, selected_table_msg) = self.__choose_target_table(csv_upload_form.select_table)

        csv_items = MasterMainteCSVItems(
            file,
            csv_upload_form.select_function,
            table_name,
            self.mst_inst_repository,
            self.emp_master_repository,
            self.bu_master_repository,
            self.emp_chginst_repository
        )

        error_message_list = []
        # CSVファイル0件(ヘッダ行のみ)チェック
        if len(csv_items.lines) == 0:
            error_message_list.append('選択されたCSVファイルの2行目以降に値が記入されておりません。')
        else:
            for row_item in csv_items:
                error_message_list.extend([data for data in row_item.validate()])

        csv_upload_list = []
        json_upload_data = ''
        if len(error_message_list) == 0:
            csv_upload_list: list[dict] = csv_items.to_dict()
            # json作成
            json_upload_data = csv_items.to_json()

        mainte_csv_up = InstEmpCsvUploadViewModel(
            is_verified=True,
            error_message_list=error_message_list,
            select_function=csv_upload_form.select_function,
            select_table=csv_upload_form.select_table,
            csv_upload_list=csv_upload_list,
            json_upload_data=json_upload_data,
            csv_file_name=csv_file_name,
            select_function_message=self.__make_dialog_confirm_message(
                csv_upload_form.select_function,
                selected_table_msg)
        )
        return mainte_csv_up

    def prepare_mainte_new_inst_view(self,
                                     user_name: str,
                                     csv_upload_form: MasterMainteCsvUpModel) -> InstEmpCsvUploadViewModel:

        (table_name, selected_table_msg) = self.__choose_target_table(csv_upload_form.select_table)

        csv_data_list = json.loads(html.unescape(csv_upload_form.unescape().json_upload_data))

        if csv_upload_form.select_function == 'new':
            emp_chg_inst = NewEmpChgInstFunction(
                csv_data_list,
                table_name,
                selected_table_msg,
                user_name,
                self.emp_chginst_repository)
        elif csv_upload_form.select_function == 'change':
            emp_chg_inst = ChangeEmpChgInstFunction(
                csv_data_list,
                table_name,
                selected_table_msg,
                user_name,
                self.emp_chginst_repository)
        else:
            raise Exception(f'機能の選択値が不正です: {csv_upload_form.select_function}')

        (result_message_list, raw_error_list) = emp_chg_inst.save()

        error_message_list = []
        error_message_list.extend(raw_error_list)

        mainte_csv_up = InstEmpCsvUploadViewModel(
            is_insert=True,
            result_message_list=result_message_list,
            error_message_list=error_message_list
        )
        return mainte_csv_up

    def copy_data_real_to_dummy(self) -> TableOverrideViewModel:
        try:
            self.emp_chginst_repository.to_jst()
            self.emp_chginst_repository.begin()
            self.emp_chginst_repository.delete_dummy_table()
            self.emp_chginst_repository.copy_real_to_dummy()
            self.emp_chginst_repository.commit()
        except Exception as e:
            self.emp_chginst_repository.rollback()
            raise e

        # コピー完了をマークして画面に返却
        table_override = TableOverrideViewModel(
            is_override=True
        )
        return table_override

    def search_emp_chg_inst_data(self, csv_download_form: MasterMainteCsvDlModel) -> pd.DataFrame:
        try:
            csv_download_form.unescape()
            # 施設担当者データを検索
            search_result_df = self.search_download_emp_chg_inst_data(csv_download_form)
        except DBException as e:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail={'error': 'db_error', 'message': e.args}
            )

        return search_result_df

    def search_download_emp_chg_inst_data(self, csv_download_form: MasterMainteCsvDlModel):
        (table_name, _) = self.__choose_target_table(csv_download_form.select_table)
        search_result_df = self.emp_chginst_repository.fetch_as_data_frame(table_name, csv_download_form)
        return search_result_df

    def write_csv_file(self, data_frame: pd.DataFrame, header: list[str], download_file_name: str):
        # csvに書き込み
        output_file_path = os.path.join(constants.MENTE_CSV_TEMPORARY_FILE_DIR_PATH, download_file_name)
        # 横長のDataFrameとするため、ヘッダーの加工処理
        header_data = {}
        for df_column, header_column in zip(data_frame.columns, header):
            header_data[df_column] = header_column

        header_df = pd.DataFrame([header_data], index=None)
        output_df = pd.concat([header_df, data_frame])
        # ヘッダー行としてではなく、1レコードとして出力する
        output_df.to_csv(output_file_path, encoding="utf-8_sig", quoting=csv.QUOTE_ALL, index=False, header=False)

        return output_file_path

    def upload_emp_chg_inst_data_file(self, df: pd.DataFrame, user_id: str, select_table: str) -> tuple[str, str]:
        if df.shape[0] == 0:
            return '該当データが存在しないためCSVファイルを出力しませんでした', ''

        # ファイル名に使用するタイムスタンプを初期化しておく
        current_timestamp = datetime.now()
        download_file_name = f'Result_{user_id}_{current_timestamp:%Y%m%d%H%M%S%f}.csv'

        # ファイルを書き出し(CSV)
        local_file_path = self.__write_emp_chg_inst_data_to_file(df, download_file_name)

        # ローカルファイルからS3にアップロードし、ダウンロード用URLを取得する
        download_file_url = ''
        try:
            bucket_name = environment.MASTER_MAINTENANCE_BUCKET
            file_key = f'data/{os.path.basename(local_file_path)}'
            self.s3_client.upload_file(local_file_path, bucket_name, file_key)
            # アップロード後、ローカルからは削除する
            self.delete_local_file(local_file_path)
            download_file_url = self.generate_download_file_url(local_file_path)
        except Exception as e:
            logger.exception(f'S3 アクセスエラー{e}')
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail={'error': 'aws_error', 'message': e.args}
            )
        if select_table == 'dummy':
            result_msg = f'ダミーテーブルのデータ{df.shape[0]}件をCSVファイルに出力しました'
        else:
            result_msg = f'本番テーブルのデータ{df.shape[0]}件をCSVファイルに出力しました'

        return result_msg, download_file_url

    def generate_download_file_url(self, local_file_path: str) -> str:
        bucket_name = environment.MASTER_MAINTENANCE_BUCKET
        file_key = f'data/{os.path.basename(local_file_path)}'
        return self.s3_client.generate_presigned_url(bucket_name, file_key, constants.MENTE_CSV_DOWNLOAD_FILE_NAME)

    def __write_emp_chg_inst_data_to_file(self, df: pd.DataFrame, download_file_name: str) -> str:
        logger.info('CSVファイルを出力する')
        local_file_path = self.write_csv_file(
            df, header=constants.MENTE_CSV_DOWNLOAD_HEADER, download_file_name=download_file_name)

        return local_file_path

    def __choose_target_table(self, select_table: str):
        if select_table == 'dummy':
            table_name = 'src05.emp_chg_inst_wrk'
            selected_table_msg = constants.CSV_CHANGE_TABLE_NAME
        elif select_table == 'real':
            table_name = 'src05.emp_chg_inst'
            selected_table_msg = constants.CSV_REAL_TABLE_NAME
        else:
            raise Exception(f'登録テーブルの選択値が不正です: {select_table}')
        return (table_name, selected_table_msg)

    def __make_dialog_confirm_message(self, select_function: str, selected_table_msg: str) -> str:
        select_function_msg = '新規施設登録' if select_function == 'new' else '施設担当者変更'
        return f'{selected_table_msg}に{select_function_msg}を行いますか？'

    def delete_local_file(self, local_file_path: str):
        os.remove(local_file_path)
