"""
VPS内MySQL バックアップ＆FTP転送スクリプト

処理フロー:
  1. mysqldump で VPS内DB (rankradar_data) をバックアップ
  2. FTP (lerisa.kir.jp) に転送
  3. ローカルの .sql ファイルを削除
  4. FTPサーバー上の30日以上古いファイルを削除

Usage:
    python tools/backup.py <client_id>
    例: python tools/backup.py 1  → client_id=00001 として処理
"""

import argparse
import ftplib
import logging
import os
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path

sys.path.append(str(Path(__file__).resolve().parents[1]))
from config.database import execute_update  # インポート時に load_dotenv() が自動実行される

# ---------------------------------------------------------------------------
# 定数
# ---------------------------------------------------------------------------
MYSQLDUMP_PATH = r"C:\Program Files\MySQL\MySQL Server 8.4\bin\mysqldump.exe"
BACKUP_DIR     = Path(r"C:\backup")

FTP_HOST   = "lerisa.kir.jp"
FTP_USER   = "lerisa..rankradar"
FTP_PASS   = "stance0209P0531#"
FTP_ROOT   = ""                      # FTP接続後のルートが /public_html/rankradar/ のため空文字
FTP_RETAIN_DAYS = 30                 # FTP上の保持日数

# ---------------------------------------------------------------------------
# ロガー設定
# ---------------------------------------------------------------------------
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# エラーログ記録
# ---------------------------------------------------------------------------
def save_error_log(client_id_str: str, message: str) -> None:
    """カゴヤDB の error_logs テーブルにエラーを記録する（local=False）。"""
    sql = """
        INSERT INTO error_logs
            (client_id, media_name, error_message, occurred_at)
        VALUES (%s, %s, %s, %s)
    """
    try:
        # client_id は整数に変換（変換できない場合は 0 を使用）
        try:
            cid = int(client_id_str)
        except (ValueError, TypeError):
            cid = 0
        execute_update(sql, (cid, "backup", message, datetime.now()), local=False)
    except Exception as e:
        logger.error("エラーログのDB保存も失敗: %s", e)


# ---------------------------------------------------------------------------
# Step 1: mysqldump バックアップ
# ---------------------------------------------------------------------------
def run_mysqldump(client_id_str: str) -> Path:
    """
    mysqldump を実行して .sql ファイルを C:\\backup\\ に保存する。
    Returns: 作成したファイルパス
    """
    db_user     = os.getenv("DB_LOCAL_USER")
    db_password = os.getenv("DB_LOCAL_PASSWORD")
    db_name     = os.getenv("DB_LOCAL_NAME")

    if not all([db_user, db_password, db_name]):
        raise EnvironmentError(
            "DB_LOCAL_USER / DB_LOCAL_PASSWORD / DB_LOCAL_NAME が .env に設定されていません"
        )

    # バックアップ先フォルダが存在しない場合は自動作成
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)

    date_str  = datetime.now().strftime("%Y%m%d")
    file_name = f"rankradar_{client_id_str}_{date_str}.sql"
    file_path = BACKUP_DIR / file_name

    logger.info("mysqldump 開始: %s", file_path)

    cmd = [
        MYSQLDUMP_PATH,
        f"--user={db_user}",
        f"--password={db_password}",
        "--single-transaction",
        "--routines",
        "--triggers",
        db_name,
    ]

    with open(file_path, "w", encoding="utf-8") as f:
        result = subprocess.run(
            cmd,
            stdout=f,
            stderr=subprocess.PIPE,
            text=True,
        )

    if result.returncode != 0:
        raise RuntimeError(f"mysqldump 失敗 (code={result.returncode}): {result.stderr.strip()}")

    logger.info("mysqldump 完了: %s (%.1f KB)", file_path, file_path.stat().st_size / 1024)
    return file_path


# ---------------------------------------------------------------------------
# Step 2: FTP 転送
# ---------------------------------------------------------------------------
def ftp_upload(local_path: Path, client_id_str: str) -> None:
    """
    FTP でバックアップファイルを転送する。
    転送先フォルダが存在しない場合は自動作成する。
    """
    remote_dir  = f"/{client_id_str}"
    remote_file = f"{remote_dir}/{local_path.name}"

    logger.info("FTP接続: %s", FTP_HOST)
    with ftplib.FTP(FTP_HOST, timeout=60) as ftp:
        ftp.login(FTP_USER, FTP_PASS)
        ftp.set_pasv(True)
        logger.info("FTPログイン成功")

        # 転送先フォルダがなければ作成
        _ftp_makedirs(ftp, remote_dir)

        # ファイルをアップロード
        logger.info("FTP転送開始: %s → %s", local_path.name, remote_file)
        with open(local_path, "rb") as f:
            ftp.storbinary(f"STOR {remote_file}", f)
        logger.info("FTP転送完了: %s", remote_file)

        # 30日以上古いファイルを削除
        _ftp_delete_old_files(ftp, remote_dir)


def _ftp_makedirs(ftp: ftplib.FTP, remote_dir: str) -> None:
    """FTP上のディレクトリを再帰的に作成する。"""
    parts = [p for p in remote_dir.split("/") if p]
    current = ""
    for part in parts:
        current = f"{current}/{part}"
        try:
            ftp.cwd(current)
        except ftplib.error_perm:
            logger.info("FTPディレクトリ作成: %s", current)
            ftp.mkd(current)
            ftp.cwd(current)


def _ftp_delete_old_files(ftp: ftplib.FTP, remote_dir: str) -> None:
    """FTP上の指定ディレクトリから30日以上古いファイルを削除する。"""
    cutoff = datetime.now(tz=timezone.utc) - timedelta(days=FTP_RETAIN_DAYS)
    logger.info("FTP古いファイル削除チェック (基準: %d日前)", FTP_RETAIN_DAYS)

    try:
        ftp.cwd(remote_dir)
        # MLSD が使える場合は日時情報を取得
        entries = list(ftp.mlsd(facts=["modify", "type"]))
    except ftplib.error_perm:
        logger.warning("MLSD が利用不可。古いファイルの自動削除をスキップします。")
        return

    deleted = 0
    for name, facts in entries:
        if facts.get("type") != "file":
            continue
        modify_str = facts.get("modify", "")
        if not modify_str:
            continue
        # MLSD の modify は "YYYYMMDDHHMMSS" 形式
        try:
            modified = datetime.strptime(modify_str[:14], "%Y%m%d%H%M%S").replace(
                tzinfo=timezone.utc
            )
        except ValueError:
            continue

        if modified < cutoff:
            target = f"{remote_dir}/{name}"
            ftp.delete(target)
            logger.info("FTP古いファイル削除: %s (更新日: %s)", name, modified.date())
            deleted += 1

    if deleted == 0:
        logger.info("削除対象の古いファイルなし")


# ---------------------------------------------------------------------------
# Step 3: ローカルファイル削除
# ---------------------------------------------------------------------------
def delete_local_file(file_path: Path) -> None:
    """バックアップ完了後にローカルの .sql ファイルを削除する。"""
    file_path.unlink(missing_ok=True)
    logger.info("ローカルファイル削除: %s", file_path)


# ---------------------------------------------------------------------------
# メイン処理
# ---------------------------------------------------------------------------
def main() -> None:
    parser = argparse.ArgumentParser(description="VPS MySQL バックアップ＆FTP転送")
    parser.add_argument("client_id", type=int, help="クライアントID（例: 1 → 00001）")
    args = parser.parse_args()

    client_id_str = str(args.client_id).zfill(5)  # 5桁ゼロ埋め
    logger.info("バックアップ開始 | client_id=%s", client_id_str)

    local_file = None
    try:
        # Step 1: mysqldump
        local_file = run_mysqldump(client_id_str)

        # Step 2: FTP転送
        ftp_upload(local_file, client_id_str)

        # Step 3: ローカルファイル削除
        delete_local_file(local_file)

        logger.info("バックアップ完了 | client_id=%s", client_id_str)

    except Exception as e:
        error_msg = f"バックアップ失敗 (client_id={client_id_str}): {e}"
        logger.error(error_msg)
        save_error_log(client_id_str, error_msg)
        # ローカルファイルが残っている場合は削除
        if local_file and local_file.exists():
            delete_local_file(local_file)
        sys.exit(1)


if __name__ == "__main__":
    main()
