"""
ベンリー自動設定スクリプト（Selenium版）

analysis_suggestions から最新の suggest_times を取得し、
ベンリー（mrvenrey.jp）の「時刻指定更新」に自動入力する。

Usage:
    python tools/set_benry.py <client_id>
"""

import argparse
import logging
import re as _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
        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 _to_hhmm(value) -> str:
    """任意の時刻値を HH:MM 形式の文字列に変換する。"""
    if isinstance(value, str):
        if ":" in value:
            return value.strip()
        value = float(value)
    if isinstance(value, (int, float)):
        if value >= 100:
            h = int(value) // 100
            m = int(value) % 100
        else:
            h = int(value)
            m = round((value - h) * 60)
        return f"{h:02d}:{m:02d}"
    return str(value)


def get_latest_suggest_times(client_id: int) -> list[str]:
    """
    analysis_suggestions から最新のガールズヘブン suggest_times を取得する。
    Returns: ["09:00", "12:00", ...] などの HH:MM リスト
    """
    rows = execute_query(
        """
        SELECT suggest_time FROM analysis_suggestions
        WHERE client_id = %s AND media_name = %s
        AND DATE(suggested_at) = (
            SELECT DATE(suggested_at) FROM analysis_suggestions
            WHERE client_id = %s AND media_name = %s
            ORDER BY suggested_at DESC LIMIT 1
        )
        ORDER BY suggest_time
        """,
        (client_id, MEDIA_NAME, client_id, MEDIA_NAME),
        local=True,
    )
    if not rows:
        return []
    converted = [row["suggest_time"] for row in rows if row["suggest_time"]]
    logger.info("suggest_times 取得結果: %s", converted)
    return converted


def save_update_history(client_id: int, status: int) -> None:
    """update_histories に実行結果を記録する（local=True）。"""
    execute_update(
        """
        INSERT INTO update_histories
            (client_id, media_name, executed_at, status)
        VALUES (%s, %s, %s, %s)
        """,
        (client_id, MEDIA_NAME, datetime.now(), status),
        local=True,
    )


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 ヘルパー
# ---------------------------------------------------------------------------
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)


# ---------------------------------------------------------------------------
# ドライバー起動
# ---------------------------------------------------------------------------
def build_driver() -> webdriver.Chrome:
    """新しい空のプロファイルで Chrome を起動する。
    既存 Chrome との競合（DevToolsActivePort エラー）を避けるため
    --user-data-dir / --profile-directory は使用しない。
    """
    import os
    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=false で GUI 起動）
    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


# ---------------------------------------------------------------------------
# ベンリー自動設定本体
# ---------------------------------------------------------------------------
def run_benry(driver: webdriver.Chrome, settings: dict, suggest_times: list[str]) -> None:
    login_id   = settings["login_id"]
    login_pass = settings["login_pass"]
    tool_id    = settings["tool_id"]
    shop_name  = settings["shop_name"]

    wait = WebDriverWait(driver, 60)

    # ① ページアクセス
    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)

    # ③ ログイン処理
    login_form = find_el(driver, "input[name='user']")
    if login_form and login_form.is_displayed():
        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)

        # ログインボタンをクリック
        for xpath in [
            "//button[contains(@class,'btnLogin')]",
            "//button[contains(.,'ログイン')]",
        ]:
            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)
    else:
        logger.info("ログインフォームなし → すでにログイン済みとして続行")
        time.sleep(1)

    # ④ 上部 ID を確認・切り替え
    def _extract_digits(s: str) -> str:
        return "".join(_re.findall(r"\d+", s))

    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（shop_name='%s'）に切り替えます", tool_id, shop_name)

        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)

    # ⑤ コンテンツ更新画面へ遷移
    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(1.5)

    # ⑧ 稼働中のタイマーを停止
    for xpath in [
        "//button[contains(.,'止める')]",
        "//button[contains(.,'停止')]",
        "//button[@color='red']",
    ]:
        el = find_xpath(driver, xpath)
        if el:
            logger.info("止めるボタンをクリック: %s", xpath)
            js_click(driver, el)
            time.sleep(5)
            break
    else:
        logger.info("止めるボタンなし（スキップ）")

    # ⑨ 「すべて削除」クリック → 確認ダイアログで「削除」
    logger.info("「すべて削除」をクリック")
    for xpath in [
        "//button[contains(.,'すべて削除')]",
        "//span[contains(.,'すべて削除')]",
        "//*[contains(@class,'deleteAll')]",
    ]:
        el = find_xpath(driver, xpath)
        if el:
            logger.info("すべて削除をクリック: %s", xpath)
            js_click(driver, el)
            break
    else:
        logger.warning("「すべて削除」ボタンが見つかりませんでした")

    # ネイティブダイアログ対応（存在する場合のみ）
    try:
        alert = driver.switch_to.alert
        logger.info("ネイティブダイアログを承認: %s", alert.text)
        alert.accept()
    except Exception:
        pass

    # Angular カスタムダイアログの「削除」ボタン
    for xpath in [
        "//section[contains(@class,'modal-footer')]//button[contains(.,'削除')]",
        "//*[contains(@class,'modal-footer')]//button[contains(.,'削除')]",
        "//div[contains(@class,'modal')]//button[contains(.,'削除')]",
        "//button[contains(.,'削除')]",
    ]:
        try:
            el = WebDriverWait(driver, 5).until(
                EC.element_to_be_clickable((By.XPATH, xpath))
            )
            logger.info("確認ダイアログの削除ボタンをクリック: %s", xpath)
            js_click(driver, el)
            break
        except TimeoutException:
            continue
    time.sleep(3)

    # 入力欄が消えるまで待機（最大5秒）
    for _ in range(10):
        if not find_els(driver, "input[data-contimesettings='true']"):
            break
        time.sleep(0.5)
    logger.info("削除完了を確認")

    # ⑩ 時刻を1つずつ入力
    logger.info("時刻を入力: %s", suggest_times)

    input_selectors = [
        "input[data-contimesettings='true']",
        ".timePickerText input",
        "input.inputText",
        "textbox-ex2 input",
    ]

    def _find_inputs():
        for css in input_selectors:
            els = find_els(driver, css)
            if els:
                return els
        return []

    add_xpaths = [
        "//*[contains(@class,'addBtn')]",
        "//button[contains(@class,'add')]",
        "//*[contains(@class,'timerAdd')]",
        "//button[contains(.,'追加')]",
        "//button[contains(.,'＋')]",
        "//button[contains(.,'+')]",
    ]

    def _click_add_btn() -> bool:
        for xpath in add_xpaths:
            el = find_xpath(driver, xpath)
            if el:
                logger.info("追加ボタンをクリック: %s", xpath)
                js_click(driver, el)
                time.sleep(1)
                return True
        # contents-update-timesetting 内の最後の button を試みる
        container = find_el(driver, "contents-update-timesetting")
        if container:
            btns = container.find_elements(By.TAG_NAME, "button")
            if btns:
                logger.info("追加ボタンをクリック: contents-update-timesetting内最後のbutton")
                js_click(driver, btns[-1])
                time.sleep(1)
                return True
        logger.warning("追加ボタンが見つかりませんでした")
        return False

    inputs = _find_inputs()
    logger.info("現在の入力欄数: %d / 必要数: %d", len(inputs), len(suggest_times))

    while len(_find_inputs()) < len(suggest_times):
        before = len(_find_inputs())
        if not _click_add_btn():
            break
        if len(_find_inputs()) == before:
            logger.warning("追加ボタンをクリックしたが入力欄が増えませんでした（停止）")
            break

    for idx, t in enumerate(suggest_times):
        inputs = _find_inputs()
        if idx >= len(inputs):
            logger.warning("入力欄が足りません（idx=%d）", idx)
            break
        fill_input(driver, inputs[idx], str(t))
        inputs[idx].send_keys("\n")
        logger.info("  [%d] %s を入力", idx + 1, t)
        time.sleep(0.5)

    # ⑪ 設定を保存
    logger.info("設定を保存")
    for xpath in [
        "//button[contains(.,'設定を保存')]",
        "//*[@color='green' and contains(.,'設定を保存')]",
    ]:
        el = find_xpath(driver, xpath)
        if el:
            logger.info("設定を保存ボタンをクリック: %s", xpath)
            js_click(driver, el)
            break
    else:
        logger.warning("設定を保存ボタンが見つかりませんでした")
    time.sleep(3)

    # ⑫ 更新開始
    logger.info("更新開始")
    for xpath in [
        "//button[contains(.,'更新開始')]",
        "//*[@color='red' and contains(.,'更新開始')]",
        "//button[contains(@class,'timer_start')]",
    ]:
        el = find_xpath(driver, xpath)
        if el:
            logger.info("更新開始ボタンをクリック: %s", xpath)
            js_click(driver, el)
            break
    else:
        logger.warning("更新開始ボタンが見つかりませんでした")
    time.sleep(2)

    logger.info("ベンリー設定完了")


# ---------------------------------------------------------------------------
# エントリポイント
# ---------------------------------------------------------------------------
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 が見つかりません", 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)

    # suggest_times 取得
    try:
        suggest_times = get_latest_suggest_times(client_id)
    except Exception as e:
        logger.error("suggest_times 取得エラー: %s", e)
        save_error_log(client_id, f"suggest_times取得失敗: {e}")
        sys.exit(1)

    if not suggest_times:
        logger.warning("suggest_times が空です (client_id=%d)", client_id)
        save_error_log(client_id, f"suggest_timesが空のため設定をスキップ (client_id={client_id})")
        sys.exit(1)

    # Selenium で設定実行
    driver = None
    try:
        driver = build_driver()
        run_benry(driver, settings, suggest_times)
        save_update_history(client_id, 1)
        logger.info("完了 | client_id=%d status=1（成功）", client_id)
    except Exception as e:
        error_msg = f"ベンリー設定失敗 (client_id={client_id}): {e}"
        logger.error(error_msg)
        save_error_log(client_id, error_msg)
        try:
            save_update_history(client_id, 0)
        except Exception as db_err:
            logger.error("update_histories 記録失敗: %s", db_err)
        sys.exit(1)
    finally:
        if driver:
            driver.quit()


if __name__ == "__main__":
    main()
