"""
ガールズヘブン 最大更新回数取得スクリプト

ベンリー（mrvenrey.jp）の「時刻指定更新」画面に表示される
「残 X/Y 回」のY（上限回数）を取得し、
media_settings の max_update_count に保存する。

Usage:
    python tools/get_update_count_girlsheaven.py <client_id>
"""

import argparse
import logging
import os
import random
import re
import sys
from datetime import datetime
from pathlib import Path

from playwright.sync_api import Page, sync_playwright
from playwright_stealth import Stealth

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"
PAGE_TIMEOUT = 30_000  # ms

VIEWPORTS = [
    {"width": 1280, "height": 800},
    {"width": 1366, "height": 768},
    {"width": 1920, "height": 1080},
    {"width": 1440, "height": 900},
]

# ---------------------------------------------------------------------------
# ロガー設定
# ---------------------------------------------------------------------------
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)


# ---------------------------------------------------------------------------
# ブラウザ起動（set_benry.py と同じ設定）
# ---------------------------------------------------------------------------
def build_browser(p):
    """HEADLESS 環境変数に従いブラウザを起動し、(browser, page) を返す。"""
    headless = os.getenv("HEADLESS", "true").lower() != "false"
    if headless:
        browser = p.chromium.launch(
            headless=True,
            args=["--no-sandbox", "--disable-blink-features=AutomationControlled"],
        )
    else:
        browser = p.chromium.launch(
            headless=False,
            executable_path=r"C:\Program Files\Google\Chrome\Application\chrome.exe",
            slow_mo=500,
            args=["--no-sandbox", "--disable-blink-features=AutomationControlled"],
        )
    context = browser.new_context(
        user_agent=(
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/124.0.0.0 Safari/537.36"
        ),
        viewport=random.choice(VIEWPORTS),
        locale="ja-JP",
        timezone_id="Asia/Tokyo",
        extra_http_headers={
            "Accept": (
                "text/html,application/xhtml+xml,"
                "application/xml;q=0.9,"
                "image/avif,image/webp,*/*;q=0.8"
            ),
            "Accept-Language": "ja,en-US;q=0.7,en;q=0.3",
            "Accept-Encoding": "gzip, deflate, br",
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
            "Sec-Fetch-Dest": "document",
            "Sec-Fetch-Mode": "navigate",
            "Sec-Fetch-Site": "none",
            "Sec-Fetch-User": "?1",
        },
    )
    page = context.new_page()
    Stealth().apply_stealth_sync(page)
    return browser, page


# ---------------------------------------------------------------------------
# 上限回数取得本体
# ---------------------------------------------------------------------------
def get_update_count(page: Page, settings: dict) -> int:
    """ベンリーにログインして時刻指定更新タブの上限回数を取得して返す。"""
    login_id   = settings["login_id"]
    login_pass = settings["login_pass"]
    tool_id    = settings["tool_id"]
    shop_name  = settings["shop_name"]

    def _extract_digits(s: str) -> str:
        return "".join(re.findall(r"\d+", s))

    # ① アクセス
    logger.info("ベンリーにアクセス: %s", BENRY_URL)
    page.goto(BENRY_URL, timeout=PAGE_TIMEOUT, wait_until="domcontentloaded")
    page.wait_for_timeout(3000)

    # ② ログイン（set_benry.py と同じ処理）
    login_btn_selectors = [
        "button.btnLogin",
        "button.raised.btnLogin",
        "button:has-text('ログイン')",
    ]

    def _click_login_btn() -> None:
        for sel in login_btn_selectors:
            btn = page.locator(sel).first
            if btn.count() > 0 and btn.is_visible():
                logger.info("ログインボタンをクリック: %s", sel)
                btn.click()
                return
        raise RuntimeError("ログインボタンが見つかりませんでした")

    login_form = page.locator("input[name='user']")
    if login_form.count() > 0 and login_form.is_visible():
        logger.info("ID / パスワードを入力してログイン")
        page.locator("input[name='user']").fill(login_id)
        page.locator("input[name='pass']").fill(login_pass)
        _click_login_btn()
    else:
        logger.info("ログインフォームなし → ログインボタンをクリック")
        _click_login_btn()
    page.wait_for_timeout(3000)

    # ③ ID切り替え（tool_idの数字部分で比較）
    account_el = page.locator("p.account_info.ellipsis").first
    current_text = account_el.inner_text().strip() if account_el.count() > 0 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（shop_name='%s'）に切り替えます [数字部分: %s]", tool_id, shop_name, tool_id_digits)

        dropdown_selectors = [
            "[class*='login-picker-Container']",
            "login-picker .cursor-p",
            "login-picker div[title]",
        ]
        for sel in dropdown_selectors:
            el = page.locator(sel).first
            if el.count() > 0 and el.is_visible():
                logger.info("ドロップダウンを展開: %s", sel)
                el.click(force=True)
                break
        page.wait_for_timeout(1000)

        li_clicked = False
        for container_sel in ["login-picker", ".pickerList", ".list"]:
            for li in page.locator(f"{container_sel} li").all():
                if shop_name in li.inner_text().strip():
                    logger.info("店舗 li をクリック: '%s'", li.inner_text().strip())
                    li.click(force=True)
                    li_clicked = True
                    break
            if li_clicked:
                break
        if not li_clicked:
            logger.warning("shop_name='%s' に一致する li が見つかりませんでした", shop_name)
        page.wait_for_timeout(3000)

        new_text = page.locator("p.account_info.ellipsis").first.inner_text().strip()
        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)

    # ④ コンテンツ更新メニューをクリック
    logger.info("コンテンツ更新画面へ遷移します")
    for sel in ["a:has-text('コンテンツ更新')", "[href*='menu']", "button:has-text('コンテンツ更新')"]:
        el = page.locator(sel).first
        if el.count() > 0 and el.is_visible():
            logger.info("コンテンツ更新メニューをクリック: %s", sel)
            el.click(force=True)
            break
    page.wait_for_timeout(3000)

    # ⑤ ガールズヘブン(お店検索表示順アップ)の編集ボタンをクリック
    logger.info("ガールズヘブン上位化コンテンツを探索中...")
    rows = page.locator(".listTableItem")
    row_count = rows.count()
    logger.info(".listTableItem 件数: %d", row_count)
    edit_btn_sels = ["button:has-text('編集')", "a:has-text('編集')", "[class*='switch']"]
    edit_clicked = False

    # 戦略①：:has-text フィルターで直接特定
    direct = page.locator(
        ".listTableItem:has-text('ガールズヘブン'):has-text('お店検索表示順アップ')"
    )
    if direct.count() > 0:
        logger.info("戦略①: 直接特定成功 (%d件)", direct.count())
        for sel in edit_btn_sels:
            btn = direct.first.locator(sel).first
            if btn.count() > 0:
                logger.info("編集ボタンをクリック（戦略①）: %s", sel)
                btn.click(force=True)
                edit_clicked = True
                break
    else:
        logger.info("戦略①: 対象なし → 戦略②（全スキャン＋近傍探索）に移行")

    # 戦略②：全スキャン → target_index 前後10行を近い順に探索
    if not edit_clicked:
        target_index = None
        for i in range(row_count):
            text = rows.nth(i).inner_text()
            if "ガールズヘブン" in text:
                logger.info("ガールズヘブンを含む行を発見 [%d]: %s", i, text[:80].replace("\n", " "))
                if "お店検索表示順アップ" 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(row_count, target_index + 11)),
            key=lambda x: abs(x - target_index),
        )
        for j in search_order:
            cell = rows.nth(j)
            for sel in edit_btn_sels:
                btn = cell.locator(sel).first
                if btn.count() > 0:
                    logger.info("編集ボタンをクリック（戦略②）: index=%d sel=%s", j, sel)
                    btn.click(force=True)
                    edit_clicked = True
                    break
            if edit_clicked:
                break

    if not edit_clicked:
        raise RuntimeError("編集ボタンが見つかりません")
    page.wait_for_timeout(2000)

    # ⑥ 「時刻指定更新」タブをクリック
    logger.info("「時刻指定更新」タブをクリック")
    for sel in [
        "week-config-tab2",
        "[class*='week-config-tab2']",
        "button:has-text('時刻指定更新')",
        "a:has-text('時刻指定更新')",
    ]:
        el = page.locator(sel).first
        if el.count() > 0:
            logger.info("時刻指定更新タブをクリック: %s", sel)
            el.click(force=True)
            break
    page.wait_for_timeout(2000)

    # ⑦ 「残 X/Y 回」のテキストから Y（上限回数）を取得
    logger.info("上限回数テキストを探索中...")
    page.wait_for_timeout(5000)
    body_text = page.locator("body").inner_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)

    # ブラウザで上限回数を取得
    try:
        with sync_playwright() as p:
            browser, page = build_browser(p)
            try:
                count = get_update_count(page, settings)
            finally:
                browser.close()
    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)

    # 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()
