import os
import shutil
from datetime import datetime

import pandas as pd

from src.aws.aws_api_client import AWSAPIClient
from src.aws.s3 import S3Client
from src.logging.get_logger import get_logger
from src.model.internal.session import UserSession
from src.model.request.bio import BioModel
from src.model.view.bio_disp_model import BisDisplayModel
from src.model.view.bio_view_model import BioViewModel
from src.repositories.base_repository import BaseRepository
from src.repositories.bio_sales_view_repository import BioSalesViewRepository
from src.repositories.pharmacy_product_master_repository import \
    PharmacyProductMasterRepository
from src.repositories.wholesaler_master_repository import \
    WholesalerMasterRepository
from src.services.base_service import BaseService
from src.system_var import constants, environment

logger = get_logger('生物由来参照')


class BioViewService(BaseService):
    REPOSITORIES = {
        'whs_repository': WholesalerMasterRepository,
        'phm_repository': PharmacyProductMasterRepository,
        'bio_sales_repository': BioSalesViewRepository
    }

    CLIENTS = {
        's3_client': S3Client
    }

    whs_repository: WholesalerMasterRepository
    phm_repository: PharmacyProductMasterRepository
    bio_sales_repository: BioSalesViewRepository
    s3_client: S3Client

    def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None:
        super().__init__(repositories, clients)
        self.whs_repository = repositories['whs_repository']
        self.phm_repository = repositories['phm_repository']
        self.bio_sales_repository = repositories['bio_sales_repository']
        self.s3_client = clients['s3_client']

    def prepare_bio_view(
        self,
        session: UserSession
    ) -> BioViewModel:
        # 卸リストを取得
        wholesalers = self.whs_repository.fetch_all()
        # 製品リストを取得
        products = self.phm_repository.fetch_all()
        bio = BioViewModel(
            whs_models=wholesalers,
            phm_models=products,
            user_id=session.user_id
        )
        return bio

    def search_bio_data(self, search_params: BioModel):
        # 生物由来データを検索
        bio_sales_view_data = self.bio_sales_repository.fetch_many(parameter=search_params)
        # 画面表示用に加工
        display_bio_data: list[BisDisplayModel] = [BisDisplayModel(data) for data in bio_sales_view_data]

        return display_bio_data

    def search_download_bio_data(self, search_params: BioModel):
        # 生物由来データをダウンロードするために、DBから検索した結果をデータフレームに変換
        bio_sales_data_frame, query = self.bio_sales_repository.fetch_as_data_frame(parameter=search_params)
        return bio_sales_data_frame, query

    def write_access_log(
            self,
            query: str,
            parameters: BioModel,
            user_id: str,
            timestamp: datetime,
            download_file_name: str
    ):
        # アクセスログを書き出し、S3に保管する
        access_log_file_name = f'BioAccessLog_{user_id}_{timestamp:%Y%m%d%H%M%S%f}.log'
        # アクセスログファイル出力用のロガーを生成
        import logging
        access_logger = logging.getLogger(access_log_file_name)
        level = logging.getLevelName(environment.LOG_LEVEL)
        if not isinstance(level, int):
            level = logging.INFO
        access_logger.setLevel(level)

        access_log_file_path = os.path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, access_log_file_name)
        if not access_logger.hasHandlers():
            access_log_handler = logging.FileHandler(access_log_file_path)
            access_logger.addHandler(access_log_handler)

            formatter = logging.Formatter(
                '[%(levelname)s]\t%(asctime)s\t%(message)s',
                '%Y-%m-%d %H:%M:%S'
            )

            for handler in logger.handlers:
                handler.setFormatter(formatter)

        # SQL文を出力
        sql_message = f'ユーザーID: {user_id} SQL: {query}\t{download_file_name}'
        access_logger.info(sql_message)
        # 標準出力にも書き出す
        logger.info(sql_message)

        # 検索パラメータを1行ずつ書き出す
        for param_key, param_value in parameters.dict().items():
            if param_value is None or len(param_value) == 0:
                continue
            parameter_message = f'ユーザーID: {user_id} Value: {param_key} = {param_value}\t{download_file_name}'
            logger.info(parameter_message)
            access_logger.info(parameter_message)

        # S3にアップロード
        self.upload_bio_access_log_file(access_log_file_path)

    def write_excel_file(self, data_frame: pd.DataFrame, user_id: str, download_file_name: str):
        # Excelに書き込み
        output_file_path = os.path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, download_file_name)

        # テンプレートファイルをコピーして出力ファイルの枠だけを作る
        shutil.copyfile(
            src=constants.BIO_EXCEL_TEMPLATE_FILE_PATH,
            dst=output_file_path
        )
        # ExcelWriterの追記モード(`mode`='a')でファイルを開く
        # `engine``='openpyxlは、追記モードでExcelを開くためのおまじない(xlsxしか動作しないが、こちらが出すものなので問題ナシ)
        # 既存シートへの書き込みは、`if_sheet_exists='overlay'を指定する
        with pd.ExcelWriter(output_file_path, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer:
            # `sheet_name`引数を省略した場合は、「Sheet1」に書き込む。
            # DF内のヘッダと連番を書き込みたくない場合、`header`と`index`をFalseに指定する。
            # `startrow`と`startcol`で、Excelの書き込み位置を決定する。省略した場合はA1セルから書く。
            data_frame.to_excel(writer, header=False, index=False, startrow=1, startcol=0)

        return output_file_path

    def write_csv_file(self, data_frame: pd.DataFrame, user_id: str, header: list[str], download_file_name: str):
        # csvに書き込み
        output_file_path = os.path.join(constants.BIO_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, index=False, header=False)

        return output_file_path

    def upload_bio_data_file(self, local_file_path: str) -> None:
        bucket_name = environment.BIO_ACCESS_LOG_BUCKET
        # TODO: ファイルパスにYYYY/MM/DDを加える
        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)

    def upload_bio_access_log_file(self, local_file_path: str) -> None:
        bucket_name = environment.BIO_ACCESS_LOG_BUCKET
        # TODO: ファイルパスにYYYY/MM/DDを加える
        file_key = f'log/{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)

    def generate_download_file_url(self, local_file_path: str, user_id: str, kind: str) -> str:
        bucket_name = environment.BIO_ACCESS_LOG_BUCKET
        # TODO: ファイルパスにYYYY/MM/DDを加える
        file_key = f'data/{os.path.basename(local_file_path)}'
        download_filename = f'{user_id}_生物由来卸販売データ.{kind}'
        return self.s3_client.generate_presigned_url(bucket_name, file_key, download_filename)

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