import os
import logging
from typing import List, Dict, Any, Optional
import gspread
from google.oauth2.service_account import Credentials
from services.pigeon_cloud import PigeonCloud
from services.my_slack import send_slack_message
import time
from config.load_env import load_environment_variables
import gc


# 定数定義（共通設定）
class BaseConfig:
    BATCH_SIZE = 25
    API_WAIT_TIME = 0.4  # 秒
    STANDARD_COLUMN_COUNT = 34
    SCOPES = ['https://www.googleapis.com/auth/spreadsheets']

    # 更新対象ステータス（共通）
    TARGET_STATUSES = ["選考中", "説明会キャンセル(AG専用→CS要対応)", "説明会キャンセル(CS対応済)", "辞退依頼(AG専用→CS要対応)", "不採用", "内定承諾", "選考中辞退(CS専用)"]
    TARGET_STEPS = ["選考開始前", "説明会着座", "1次選考中", "1次選考中（キャンセル・辞退意向）", "1次選考中（日程調整中）", "2次選考中", "2次選考中（キャンセル・辞退意向）", "2次選考中（日程調整中）", "3次選考中", "3次選考中（キャンセル・辞退意向）", "3次選考中（日程調整中）", "4次選考中", "4次選考中（キャンセル・辞退意向）", "4次選考中（日程調整中）", "5次選考中", "5次選考中（キャンセル・辞退意向）", "5次選考中（日程調整中）", "最終選考前（B）", "最終選考前（B）（キャンセル・辞退意向）", "最終選考前（B）（日程調整中）", "最終選考（A）", "最終選考前（A）（キャンセル・辞退意向）", "最終選考前（A）（日程調整中）", "内定"]
    ERROR_VALUES = ["#N/A"]

    # 共通設定
    COMPANY_DATA_RANGE = 'B4:F300'
    DATA_START_ROW = 3

    # Slack設定
    CS_NOTICE_WEBHOOK_URL = os.getenv("WEBHOOK_URL_CS_NOTICE")


class ManagementStatusConfig:
    """進捗管理処理の設定クラス"""

    def __init__(
        self,
        graduation_year: str,
        worksheet_name: str,
        management_worksheet_name: str,
        completion_message: str
    ):
        """
        Args:
            graduation_year: 卒業年（"26卒" or "27卒"）
            worksheet_name: ワークシート名
            management_worksheet_name: 管理ワークシート名
            completion_message: 完了メッセージ
        """
        self.graduation_year = graduation_year
        self.worksheet_name = worksheet_name
        self.management_worksheet_name = management_worksheet_name
        self.completion_message = completion_message

    def get_completion_message(self) -> str:
        """完了メッセージを取得"""
        return self.completion_message


# Slackメッセージテンプレート
class SlackMessages:
    @staticmethod
    def step_error_message(cs_id, company_name, user_name, user_step_original, graduation_year):
        message = (
            f"<@{cs_id}>\n"
            f"卒業年：{graduation_year}\n"
            f"『{company_name}』の、ステップが記入されていません。\n"
            f"学生名：{user_name}\n"
            f"ステップ名：{user_step_original}"
        )
        return message

    @staticmethod
    def status_error_message(cs_id, company_name, user_name, user_status_original, graduation_year):
        message = (
            f"<@{cs_id}>\n"
            f"卒業年：{graduation_year}\n"
            f"『{company_name}』の、ステータスが記入されていません。\n"
            f"学生名：{user_name}\n"
            f"ステータス名：{user_status_original}"
        )
        return message

    @staticmethod
    def api_error_message(company_name, error, graduation_year):
        message = (
            f"<@U050VK2UD1B><@U082BLHC6HJ>\n"
            f"卒業年：{graduation_year}\n"
            f"スプシAPIエラーが発生しました。\n"
            f"会社名:{company_name}\n"
            f"エラー内容:{error}"
        )
        return message

    @staticmethod
    def is_not_single_entry_message(cs_id, company_name, user_name, step_status, graduation_year):
        message = (
            f"<@{cs_id}>\n"
            f"卒業年：{graduation_year}\n"
            f"下記のステップ or ステータスを更新できませんでした。\n"
            f"\n"
            f"【予想される原因】\n"
            f"①エントリーリストに「企業名×学生名」のエントリーが見当たらない、もしく複数件見つかったため\n"
            f"②学生名が完全一致していないため\n"
            f"③求人IDが進捗管理対象シートに未入力のため\n"
            f"\n"
            f"会社名：『{company_name}』\n"
            f"学生名：{user_name}\n"
            f"step or status：{step_status}"
        )
        return message

    @staticmethod
    def step_backward_message(cs_id, company_name, user_name, current_step_name, new_step_name, graduation_year):
        message = (
            f"<@{cs_id}>\n"
            f"卒業年：{graduation_year}\n"
            f"『{company_name}』の、ステップが逆戻りしています。\n"
            f"学生名：{user_name}\n"
            f"現在のステップ名：{current_step_name}\n"
            f"変更予定のステップ名：{new_step_name}"
        )
        return message

    @staticmethod
    def status_skip_message(cs_id, company_name, user_name, current_status_name, user_status, graduation_year):
        message = (
            f"<@{cs_id}>\n"
            f"卒業年：{graduation_year}\n"
            f"『{company_name}』のステータスは変更されませんでした。\n"
            f"学生名：{user_name}\n"
            f"現在のステータス：{current_status_name}\n"
            f"変更予定のステータス：{user_status}\n"
            f"理由：現在のステータスが「{current_status_name}」のため"
        )
        return message


def process_company_data(
    company_management_value: List[str],
    company_name: str,
    company_job_id: str,
    front_cs_slack_id: Optional[str],
    config: ManagementStatusConfig,
    pigeon: PigeonCloud
) -> None:
    """
    一企業の学生データを処理し、進捗状況を更新する

    Args:
        company_management_value: シートから取得した行データ
        company_name: 企業名
        company_job_id: 企業求人ID
        front_cs_slack_id: CS担当者のSlack ID
        config: 設定オブジェクト
        pigeon: PigeonCloudインスタンス
    """
    # 列数の標準化（最低9列必要: インデックス0-8）
    while len(company_management_value) < 9:
        company_management_value.append('')

    user_step_original = company_management_value[2]
    user_status_original = company_management_value[3]
    user_name = company_management_value[4]
    user_step = company_management_value[6]
    user_status = company_management_value[7]
    user_undo_text = company_management_value[8]

    if not user_name:
        return

    try:
        process_entry_data(
            company_job_id, user_name, user_status,
            user_step, user_undo_text,
            company_name, front_cs_slack_id,
            user_status_original, user_step_original,
            pigeon, config
        )

    except Exception as e:
        print(f"""求人情報更新中に、エラー発生
            会社名:{company_name}
            ユーザー名:{user_name}
            エラー内容:{e}
            """)
        message = SlackMessages.api_error_message(company_name, e, graduation_year=config.graduation_year)
        send_slack_message(message, BaseConfig.CS_NOTICE_WEBHOOK_URL)


def process_entry_data(
    id_value: str,
    user_name: str,
    user_status: str,
    user_step: str,
    user_undo_text: str,
    company_name: str,
    front_cs_slack_id: Optional[str],
    user_status_original: str,
    user_step_original: str,
    pigeon: PigeonCloud,
    config: Optional[ManagementStatusConfig] = None
) -> None:
    """エントリーデータの処理"""

    if config is None:
        config = ManagementStatusConfig()

    # エントリーデータを取得
    entry_data = pigeon.fetch_job_entry_data(id_value, user_name)
    if not is_single_entry(entry_data, company_name, user_name, front_cs_slack_id, user_status, config):
        return

    if not entry_data:
        return

    entry_id = entry_data['data'][0]['raw_data']['id']
    
    # 更新するデータを準備
    update_step = None
    update_status = None
    update_fields = []

    # ステータスの変更チェック
    if user_status in BaseConfig.TARGET_STATUSES:
        current_status_name = entry_data['data'][0]['view_data']['field__343']
        
        # 現在のステータスが特定のものの場合は変更をしない
        if current_status_name in ["辞退依頼(AG専用→CS要対応)", "辞退企業連絡(RA→CS要対応)", "選考中辞退(CS専用)", "内定後辞退(CS専用)", "承諾後辞退(CS専用)", "再予約中", "不採用", "枠待ち"]:
            # 辞退依頼またはキャンセル、再予約のステータスで、現在のステータスと異なる場合は更新せず、Slack通知を送信
            if current_status_name != user_status:

                message = SlackMessages.status_skip_message(
                    front_cs_slack_id, company_name, user_name,
                    current_status_name, user_status, config.graduation_year
                )
                send_slack_message(message, BaseConfig.CS_NOTICE_WEBHOOK_URL)

        else:
            user_status_id = pigeon.status_convert_id(user_status)
            update_status = user_status_id
            update_fields.append('status')
            
    if user_status in BaseConfig.ERROR_VALUES:
        message = SlackMessages.status_error_message(front_cs_slack_id, company_name, user_name, user_status_original, graduation_year=config.graduation_year)
        send_slack_message(message, BaseConfig.CS_NOTICE_WEBHOOK_URL)

    # ステップの変更チェック
    if user_step in BaseConfig.TARGET_STEPS:
        # 不可逆のチェックを行うため、現在のステップの順序を確認
        current_step_order = entry_data['data'][0]['raw_data']['field__1316']
        # 新しいステップの順序を取得
        new_step_order = pigeon.get_step_order(user_step)
        
        # 不可逆チェック: 現在のステップ順序 >= 新しいステップ順序 の場合は更新しない
        if current_step_order is not None and new_step_order is not None:
            if current_step_order > new_step_order:
                # 現在のステップ名を取得
                current_step_name = entry_data['data'][0]['view_data']['field__330']
                print(f'ステップ後戻り防止: 会社名={company_name}, ユーザー名={user_name}, 現在順序={current_step_order}, 新順序={new_step_order}')

                # 後戻りの場合はSlack通知を送信
                message = SlackMessages.step_backward_message(
                    front_cs_slack_id, company_name, user_name,
                    current_step_name, user_step, config.graduation_year
                )
                send_slack_message(message, BaseConfig.CS_NOTICE_WEBHOOK_URL)
            else:
                # 順序が進む場合のみ更新
                user_step_id = pigeon.step_convert_id(user_step)
                update_step = user_step_id
                update_fields.append('step')
        else:
            # 順序が取得できない場合は従来通り更新
            user_step_id = pigeon.step_convert_id(user_step)
            update_step = user_step_id
            update_fields.append('step')

    if user_step in BaseConfig.ERROR_VALUES:
        message = SlackMessages.step_error_message(front_cs_slack_id, company_name, user_name, user_step_original, graduation_year=config.graduation_year)
        send_slack_message(message, BaseConfig.CS_NOTICE_WEBHOOK_URL)


    # 更新が必要なフィールドがある場合、一括で更新
    if update_fields:

        #ステップ、ステータスを更新
        pigeon.update_entry_data_all(
            entry_id,
            user_step=update_step,
            user_status=update_status,
        )
        time.sleep(BaseConfig.API_WAIT_TIME)  # API処理後の待機時間

    ##歩留まりタスクへの対応
    #歩留まりタスクが存在する場合は、過去に同じタスクがなければ、新規作成。あれば、なにもしない。
    if user_undo_text:
        if not user_undo_text in BaseConfig.ERROR_VALUES:
            res = pigeon.get_user_undo_task(entry_id, user_undo_text) # DBにすでに存在するタスクかどうかの確認
            record_count = res["count"]

            # countが0 → 新規作成
            if record_count == 0:
                user_step_status = user_step_original + user_status_original
                pigeon.create_user_undo_task(entry_id, user_undo_text, user_step_status)

            else:
                # 1件以上あれば何もしない（必要に応じてログだけ出す）
                print(f"[SKIP] {record_count}件のUNDOタスクが既に存在します。")

    #未予約タスクが存在しない場合は、過去の「未対応」ステータスタスクをすべて「対応完了」ステータスに
    else:
        #過去の歩留まりタスク✕ステータスが未対応、対応中を取得
        latest_undo_tasks = pigeon.get_user_latest_undo_task(entry_id)
        # id だけを抽出してリスト化
        latest_undo_task_ids = [item["raw_data"]["id"] for item in latest_undo_tasks["data"]]
        for latest_undo_task_id in latest_undo_task_ids:
            pigeon.update_user_undo_task_status(latest_undo_task_id)

    time.sleep(BaseConfig.API_WAIT_TIME)  # API処理後の待機時間                        
    print(f'会社名：{company_name}\nユーザー名：{user_name}\n更新完了: {", ".join(update_fields)}')


def is_single_entry(
    entry_data: Dict[str, Any],
    company_name: str,
    user_name: str,
    front_cs_slack_id: Optional[str],
    user_step_or_status: str,
    config: ManagementStatusConfig
) -> bool:
    """
    エントリーデータの件数をチェックし、適切な件数(１件)かどうかを判定

    Args:
        entry_data: エントリー情報
        company_name: 企業名
        user_name: 学生名
        front_cs_slack_id: CS担当者Slack ID
        user_step_or_status: ステップまたはステータス情報
        config: 設定オブジェクト

    Returns:
        bool: 処理続行可能かどうか
    """
    list_count = entry_data["count"]

    if list_count == 1:
        return True
    
    print(f'{user_name}名前重複、もしくは該当者なしでエントリーリストが0 or 2件以上発生しました')
    message = SlackMessages.is_not_single_entry_message(front_cs_slack_id, company_name, user_name, user_step_or_status, graduation_year=config.graduation_year)
    send_slack_message(message, BaseConfig.CS_NOTICE_WEBHOOK_URL)
    return False


def setup_logging() -> None:
    """ロギングの設定を行う"""
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[logging.FileHandler("app.log")]
    )


def setup_google_sheets() -> gspread.client.Client:
    """Google Sheets APIクライアントのセットアップを行う"""
    PROD_SERVICE_ACCOUNT_FILE = os.getenv('PROD_SERVICE_ACCOUNT_FILE')

    if not PROD_SERVICE_ACCOUNT_FILE:
        raise ValueError("環境変数 'PROD_SERVICE_ACCOUNT_FILE' が設定されていません。.env.production ファイルを確認してください。")

    if not os.path.exists(PROD_SERVICE_ACCOUNT_FILE):
        raise FileNotFoundError(f"サービスアカウントファイルが見つかりません: {PROD_SERVICE_ACCOUNT_FILE}")

    creds = Credentials.from_service_account_file(PROD_SERVICE_ACCOUNT_FILE, scopes=BaseConfig.SCOPES)
    return gspread.authorize(creds)


def get_company_list(client: gspread.client.Client, config: ManagementStatusConfig) -> List[List[str]]:
    """メインスプレッドシートから企業一覧を取得する"""
    SPREADSHEET_ID = os.getenv("SPREADSHEET_ID")
    sheet = client.open_by_key(SPREADSHEET_ID)
    worksheet = sheet.worksheet(config.worksheet_name)
    return worksheet.get(BaseConfig.COMPANY_DATA_RANGE)


def process_company_worksheet(
    client: gspread.client.Client,
    company_value: List[str],
    config: ManagementStatusConfig,
    pigeon: PigeonCloud
) -> None:
    """個別企業のシートを処理する"""

    # 列数の標準化（最低5列必要）
    while len(company_value) < 5:
        company_value.append('')

    company_name = company_value[0]
    company_spreadsheet_id = company_value[1]
    company_job_id = company_value[3]
    company_id_for_cs_slack = company_value[4]

    # 必須データのチェック
    if not company_name or not company_spreadsheet_id:
        print(f"スキップ: 企業名またはスプレッドシートIDが未入力")
        return

    if not company_job_id:
        print(f"スキップ: {company_name} - 求人IDが未入力")
        return

    # CS Slack IDの取得
    front_cs_slack_id = None
    try:
        front_cs_slack_id = pigeon.fetch_cs_front_slack_id(company_id_for_cs_slack)
    except Exception as e:
        print(f"CS Slack ID取得エラー - 企業: {company_name}, エラー: {e}")

    try:
        sheet_management = client.open_by_key(company_spreadsheet_id)
        worksheet_management = sheet_management.worksheet(config.management_worksheet_name)

        # A列のデータを取得
        all_values = worksheet_management.get('A:A')
        last_row = len(all_values) + BaseConfig.DATA_START_ROW
        company_management_values = worksheet_management.get(f"A{BaseConfig.DATA_START_ROW}:AH{last_row}")

        # 列数の標準化
        for row in company_management_values:
            while len(row) < BaseConfig.STANDARD_COLUMN_COUNT:
                row.append('')

        # 各行のデータを処理
        for company_management_value in company_management_values:
            process_company_data(
                company_management_value, company_name,
                company_job_id, front_cs_slack_id, config, pigeon
            )

    except Exception as e:
        error_msg = f"シート処理エラー - 企業: {company_name}, エラー: {str(e)}"
        print(error_msg)
        message = SlackMessages.api_error_message(company_name, e, graduation_year=config.graduation_year)
        send_slack_message(message, BaseConfig.CS_NOTICE_WEBHOOK_URL)


def run_management_status(config: ManagementStatusConfig) -> None:
    """
    進捗管理のメイン処理（統合版）

    Args:
        config: ManagementStatusConfig 設定オブジェクト
    """
    from utils.memory_logger import log_memory_usage

    log_memory_usage("management_status_start")

    try:
        load_environment_variables()

        # 初期セットアップ
        setup_logging()
        client = setup_google_sheets()

        # PigeonCloudインスタンスを1回だけ作成
        pigeon = PigeonCloud()

        # 企業データ取得
        company_values = get_company_list(client, config)
        log_memory_usage("spreadsheet_data_loaded")

        # バッチ処理
        for i in range(0, len(company_values), BaseConfig.BATCH_SIZE):
            batch = company_values[i:i + BaseConfig.BATCH_SIZE]

            for company_value in batch:
                process_company_worksheet(
                    client, company_value, config, pigeon
                )

            # バッチ処理後のクリーンアップ
            gc.collect()
            log_memory_usage("batch_processing_end", f"Batch: {i}-{i+BaseConfig.BATCH_SIZE}")

        # 最終処理
        completion_message = config.get_completion_message()
        send_slack_message(completion_message, BaseConfig.CS_NOTICE_WEBHOOK_URL)

    except Exception as e:
        print(e)
        log_memory_usage("error", f"General Error: {str(e)}")

    finally:
        log_memory_usage("management_status_end")
        gc.collect()
