"""
ガールズヘブン 最大更新回数取得スクリプト（Selenium版）

ベンリー（mrvenrey.jp）の「時刻指定更新」画面に表示される
「残 X/Y 回」のY（上限回数）を取得し、
media_settings の max_update_count に保存する。

ログイン処理・店舗選択処理は set_benry.py（動作実証済み）と同じ Selenium 方式を使用。
  - GRP系 (login_id が "GRP" で始まる): ログイン後に login-picker で店舗を選択する
  - I系   (login_id が "I"   で始まる): 店舗選択をスキップしてそのまま更新回数取得へ進む

Usage:
    python tools/get_update_count_girlsheaven.py <client_id>
"""

import argparse
import logging
import os
import re
import sys
import time
from datetime import datetime
from pathlib import Path

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from webdriver_manager.chrome import ChromeDriverManager

sys.path.append(str(Path(__file__).resolve().parents[1]))
from config.database import execute_query, execute_update  # load_dotenv() は自動実行

# ---------------------------------------------------------------------------
# 定数
# ---------------------------------------------------------------------------
BENRY_URL  = "https://mrvenrey.jp/#/admin"
MEDIA_NAME = "girlsheaven"

# ---------------------------------------------------------------------------
# ロガー設定
# ---------------------------------------------------------------------------
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# DB ヘルパー
# ---------------------------------------------------------------------------
def get_tool_settings(client_id: int) -> dict | None:
    """カゴヤDB の tool_settings と clients を JOIN して設定を取得する。"""
    rows = execute_query(
        """
        SELECT t.login_id, t.login_pass, t.tool_id, t.is_active, c.shop_name
        FROM tool_settings t
        JOIN clients c ON c.id = t.client_id
        WHERE t.client_id = %s AND t.tool_name = 'benry'
        LIMIT 1
        """,
        (client_id,),
        local=False,
    )
    if not rows:
        return None
    row = rows[0]
    return {
        "login_id":   row["login_id"],
        "login_pass": row["login_pass"],
        "tool_id":    row["tool_id"],
        "is_active":  row["is_active"],
        "shop_name":  row["shop_name"],
    }


def save_max_update_count(client_id: int, count: int) -> None:
    """media_settings の max_update_count を更新する（local=False）。"""
    execute_update(
        """
        UPDATE media_settings
        SET max_update_count = %s
        WHERE client_id = %s AND media_name = %s
        """,
        (count, client_id, MEDIA_NAME),
        local=False,
    )
    logger.info("max_update_count を %d に保存しました (client_id=%d)", count, client_id)


def save_error_log(client_id: int, message: str) -> None:
    """error_logs にエラーを記録する（local=False）。"""
    try:
        execute_update(
            """
            INSERT INTO error_logs
                (client_id, error_type, error_message, occurred_at)
            VALUES (%s, %s, %s, %s)
            """,
            (client_id, "benry_error", message, datetime.now()),
            local=False,
        )
    except Exception as e:
        logger.error("エラーログのDB保存も失敗: %s", e)


# ---------------------------------------------------------------------------
# Selenium ヘルパー（set_benry.py と共通）
# ---------------------------------------------------------------------------
def find_el(driver, css: str):
    """CSS セレクタで最初の要素を返す（なければ None）。"""
    els = driver.find_elements(By.CSS_SELECTOR, css)
    return els[0] if els else None


def find_els(driver, css: str) -> list:
    """CSS セレクタで要素リストを返す。"""
    return driver.find_elements(By.CSS_SELECTOR, css)


def find_xpath(driver, xpath: str):
    """XPath で最初の要素を返す（なければ None）。"""
    els = driver.find_elements(By.XPATH, xpath)
    return els[0] if els else None


def find_xpaths(driver, xpath: str) -> list:
    """XPath で要素リストを返す。"""
    return driver.find_elements(By.XPATH, xpath)


def js_click(driver, el) -> None:
    """JavaScript 経由でクリックする（force クリック相当）。"""
    driver.execute_script("arguments[0].click()", el)


def fill_input(driver, el, value: str) -> None:
    """入力欄をクリアして値を入力する。"""
    driver.execute_script("arguments[0].value = ''", el)
    el.send_keys(value)


# ---------------------------------------------------------------------------
# ドライバー起動（set_benry.py と同じ設定）
# ---------------------------------------------------------------------------
def build_driver() -> webdriver.Chrome:
    """新しい空のプロファイルで Chrome を起動する。"""
    options = Options()
    options.binary_location = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_argument("--no-first-run")
    options.add_argument("--no-default-browser-check")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option("useAutomationExtension", False)

    headless = os.getenv("HEADLESS", "true").lower() != "false"
    if headless:
        options.add_argument("--headless=new")
        options.add_argument("--window-size=1920,1080")

    driver = webdriver.Chrome(
        service=Service(ChromeDriverManager().install()),
        options=options,
    )
    logger.info("Chromeドライバー起動完了 (headless=%s)", headless)
    return driver


# ---------------------------------------------------------------------------
# 上限回数取得本体（Selenium版）
# ---------------------------------------------------------------------------
def get_update_count(driver: webdriver.Chrome, settings: dict) -> int:
    """ベンリーにログインして時刻指定更新タブの上限回数を取得して返す。"""
    login_id   = settings["login_id"]
    login_pass = settings["login_pass"]
    tool_id    = settings["tool_id"]
    shop_name  = settings["shop_name"]

    wait = WebDriverWait(driver, 60)

    def _extract_digits(s: str) -> str:
        return "".join(re.findall(r"\d+", s))

    # GRP系 / I系 の判定
    is_grp = login_id.upper().startswith("GRP")
    logger.info(
        "login_id=%s → %s系（店舗選択: %s）",
        login_id, "GRP" if is_grp else "I", "あり" if is_grp else "スキップ",
    )

    # ① アクセス
    logger.info("ベンリーにアクセス: %s", BENRY_URL)
    driver.get(BENRY_URL)

    # ② Angular 起動完了を待つ
    logger.info("Angular アプリ起動待機（最大60秒）")
    try:
        wait.until(
            lambda d: d.execute_script(
                "return document.querySelector('.loading') === null"
            )
        )
        logger.info("Angular アプリ起動完了")
    except TimeoutException:
        logger.warning("Angular 起動待機タイムアウト（処理続行）")
    time.sleep(2)

    # ③ ログイン処理（set_benry.py の統一ロジックを流用）
    def _try_alt_account_click_se() -> bool:
        """「別アカウント」を含む要素をクリックして True を返す。見つからなければ False。"""
        for kw in ["別アカウント", "別のアカウント"]:
            for tag in ["a", "button", "span", "div"]:
                els = driver.find_elements(By.XPATH, f"//{tag}[contains(.,'{kw}')]")
                for el in els:
                    try:
                        if el.is_displayed():
                            logger.info("「%s」要素をクリック: %s", kw, tag)
                            js_click(driver, el)
                            return True
                    except Exception:
                        pass
        return False

    login_form_visible = False
    login_form = None
    try:
        logger.info("input[name='user'] の表示を待機中（最大20秒）...")
        login_form = WebDriverWait(driver, 20).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "input[name='user']"))
        )
        login_form_visible = True
        logger.info("input[name='user'] が表示されました")
    except TimeoutException:
        logger.info("input[name='user'] がタイムアウト → 「別アカウント」クリックを試みます")
        if _try_alt_account_click_se():
            time.sleep(2)
            try:
                login_form = WebDriverWait(driver, 10).until(
                    EC.visibility_of_element_located((By.CSS_SELECTOR, "input[name='user']"))
                )
                login_form_visible = True
                logger.info("「別アカウント」クリック後に input[name='user'] が表示されました")
            except TimeoutException:
                logger.info("「別アカウント」後もフォームが出ません → ログイン済みとみなして続行")
        else:
            logger.info("「別アカウント」要素も見つからず → ログイン済みとみなして続行")

    if login_form_visible and login_form:
        logger.info("ログインフォームあり → ID/PW入力してログイン")
        login_form.clear()
        login_form.send_keys(login_id)
        pw_el = find_el(driver, "input[name='pass']")
        if pw_el:
            pw_el.clear()
            pw_el.send_keys(login_pass)

        # ログインボタン: テキスト優先（新UI: button[class="flex-c-c"] 内の span "ログイン"）
        for xpath in [
            "//button[contains(.,'ログイン')]",
            "//button[contains(@class,'btnLogin')]",
        ]:
            btn = find_xpath(driver, xpath)
            if btn and btn.is_displayed():
                logger.info("ログインボタンをクリック: %s", xpath)
                js_click(driver, btn)
                break

        # ログイン後の Angular 再起動を待つ
        try:
            wait.until(
                lambda d: d.execute_script(
                    "return document.querySelector('.loading') === null"
                )
            )
        except TimeoutException:
            pass
        time.sleep(3)
        logger.info("ログイン処理完了")
    else:
        logger.info("ログインフォームなし → ログイン済みとみなして続行")
        time.sleep(1)

    # ④ 店舗選択（GRP系のみ）
    if is_grp:
        logger.info("GRP系 → login-picker で店舗を選択します (tool_id=%s, shop_name=%s)", tool_id, shop_name)

        account_el = find_el(driver, "p.account_info.ellipsis")
        current_text = account_el.text.strip() if account_el else ""
        logger.info("現在のID表示: '%s'", current_text)

        tool_id_digits = _extract_digits(tool_id)
        id_matched = bool(tool_id_digits) and tool_id_digits in _extract_digits(current_text)

        if not id_matched:
            logger.info("ID を %s に切り替えます [数字部分: %s]", tool_id, tool_id_digits)

            for css in [
                "[class*='login-picker-Container']",
                "login-picker .cursor-p",
                "login-picker div[title]",
            ]:
                el = find_el(driver, css)
                if el and el.is_displayed():
                    logger.info("ドロップダウンを展開: %s", css)
                    js_click(driver, el)
                    break
            time.sleep(1)

            li_clicked = False
            for container_css in ["login-picker li", ".pickerList li", ".list li"]:
                for li in find_els(driver, container_css):
                    if shop_name in li.text.strip():
                        logger.info("店舗 li をクリック: '%s'", li.text.strip())
                        js_click(driver, li)
                        li_clicked = True
                        break
                if li_clicked:
                    break
            if not li_clicked:
                logger.warning("shop_name='%s' に一致する li が見つかりませんでした", shop_name)
            time.sleep(3)

            account_el2 = find_el(driver, "p.account_info.ellipsis")
            new_text = account_el2.text.strip() if account_el2 else ""
            if tool_id_digits and tool_id_digits in _extract_digits(new_text):
                logger.info("ID切り替え完了: '%s'", new_text)
            else:
                logger.warning("ID切り替え確認失敗: expected=%s actual='%s'", tool_id, new_text)
        else:
            logger.info("ID は既に一致しています (tool_id_digits=%s, current='%s')", tool_id_digits, current_text)
    else:
        logger.info("I系 → 店舗選択をスキップ")

    # ⑤ コンテンツ更新メニューをクリック
    logger.info("コンテンツ更新メニューをクリック")
    for xpath in [
        "//a[contains(.,'コンテンツ更新')]",
        "//button[contains(.,'コンテンツ更新')]",
    ]:
        el = find_xpath(driver, xpath)
        if el and el.is_displayed():
            logger.info("コンテンツ更新メニューをクリック: %s", xpath)
            js_click(driver, el)
            break
    else:
        logger.warning("コンテンツ更新メニューが見つかりませんでした")
    time.sleep(3)

    # ⑥ ガールズヘブン × お店検索表示順アップ の「編集」ボタンをクリック
    logger.info("ガールズヘブン上位化コンテンツを探索中...")
    edit_xpaths = [
        "//button[contains(.,'編集')]",
        "//a[contains(.,'編集')]",
    ]
    edit_clicked = False

    # 戦略①：両キーワードを含む .listTableItem を直接特定
    direct_rows = find_xpaths(
        driver,
        "//*[contains(@class,'listTableItem') and contains(.,'ガールズヘブン') and contains(.,'お店検索表示順アップ')]",
    )
    if direct_rows:
        logger.info("戦略①: 直接特定成功 (%d件)", len(direct_rows))
        for xpath in edit_xpaths:
            btns = direct_rows[0].find_elements(By.XPATH, "." + xpath)
            if btns:
                logger.info("編集ボタンをクリック（戦略①）: %s", xpath)
                js_click(driver, btns[0])
                edit_clicked = True
                break
    else:
        logger.info("戦略①: 対象なし → 戦略②（全スキャン）に移行")

    if not edit_clicked:
        all_rows = find_els(driver, ".listTableItem")
        target_index = None
        for i, row in enumerate(all_rows):
            text = row.text
            if "ガールズヘブン" in text and "お店検索表示順アップ" in text:
                target_index = i
                break
        if target_index is None:
            raise ValueError("ガールズヘブン × お店検索表示順アップ のコンテンツ行が見つかりません")

        logger.info("戦略②: target_index=%d の前後10行を探索", target_index)
        search_order = sorted(
            range(max(0, target_index - 10), min(len(all_rows), target_index + 11)),
            key=lambda x: abs(x - target_index),
        )
        for j in search_order:
            cell = all_rows[j]
            for xpath in edit_xpaths:
                btns = cell.find_elements(By.XPATH, "." + xpath)
                if btns:
                    logger.info("編集ボタンをクリック（戦略②）: index=%d xpath=%s", j, xpath)
                    js_click(driver, btns[0])
                    edit_clicked = True
                    break
            if edit_clicked:
                break

    if not edit_clicked:
        raise RuntimeError("編集ボタンが見つかりません")
    time.sleep(2)

    # ⑦ 「時刻指定更新」タブをクリック
    logger.info("「時刻指定更新」タブをクリック")
    for xpath in [
        "//week-config-tab2",
        "//*[contains(@class,'week-config-tab2')]",
        "//button[contains(.,'時刻指定更新')]",
        "//a[contains(.,'時刻指定更新')]",
    ]:
        el = find_xpath(driver, xpath)
        if el:
            logger.info("時刻指定更新タブをクリック: %s", xpath)
            js_click(driver, el)
            break
    else:
        logger.warning("時刻指定更新タブが見つかりませんでした")
    time.sleep(2)

    # ⑧ 「残 X/Y 回」のテキストから Y（上限回数）を取得
    logger.info("上限回数テキストを探索中...")
    time.sleep(5)  # タブ遷移後の描画待機
    body_text = driver.find_element(By.TAG_NAME, "body").text
    match = re.search(r'残\s*(\d+)\s*/\s*(\d+)\s*回', body_text)
    if match:
        current = int(match.group(1))
        max_count = int(match.group(2))
        logger.info("上限回数取得成功: 残%d/%d回 → 上限=%d", current, max_count, max_count)
    else:
        raise RuntimeError("上限回数（残 X/Y 回）のテキストが見つかりません")

    return max_count


# ---------------------------------------------------------------------------
# エントリポイント
# ---------------------------------------------------------------------------
def main() -> None:
    parser = argparse.ArgumentParser(description="ガールズヘブン 最大更新回数取得")
    parser.add_argument("client_id", type=int, help="クライアントID")
    args = parser.parse_args()
    client_id = args.client_id

    logger.info("ガールズヘブン更新回数取得開始 | client_id=%d", client_id)

    # tool_settings 取得
    try:
        settings = get_tool_settings(client_id)
    except Exception as e:
        logger.error("tool_settings 取得エラー: %s", e)
        save_error_log(client_id, f"tool_settings取得失敗: {e}")
        sys.exit(1)

    if settings is None:
        logger.error("client_id=%d の tool_settings(benry) が見つかりません", client_id)
        save_error_log(client_id, f"tool_settingsが見つかりません (client_id={client_id})")
        sys.exit(1)

    if settings["is_active"] == 0:
        logger.info("is_active=0 のためスキップします (client_id=%d)", client_id)
        sys.exit(0)

    # Selenium で上限回数を取得
    driver = None
    try:
        driver = build_driver()
        count = get_update_count(driver, settings)
    except Exception as e:
        error_msg = f"更新回数取得失敗 (client_id={client_id}): {e}"
        logger.error(error_msg)
        save_error_log(client_id, error_msg)
        sys.exit(1)
    finally:
        if driver:
            driver.quit()

    # max_update_count を保存
    try:
        save_max_update_count(client_id, count)
    except Exception as e:
        error_msg = f"max_update_count保存失敗 (client_id={client_id}): {e}"
        logger.error(error_msg)
        save_error_log(client_id, error_msg)
        sys.exit(1)

    logger.info("完了 | client_id=%d max_update_count=%d", client_id, count)


if __name__ == "__main__":
    main()
