#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Tarjan OJ 自动签到工具（专业版）
功能：
- 自动登录并签到
- 凭据存储于 Windows 凭据管理器（keyring）
- 自动创建每12小时执行的定时任务（schtasks）
- 日志记录在 C:\\TarjanOJ\\logs\\checkin_YYYY-MM-DD.log
- 全程详细的命令行反馈

作者: ling-voc
GitHub: https://github.com/ling-voc
"""

# ========== 水印 ==========
WATERMARK = r"""   _    __
  | |  / /___  _____
  | | / / __ \/ ___/
  | |/ / /_/ / /__
  |___/\____/\___/
  
GitHub: https://github.com/ling-voc
"""

import sys
import os
import subprocess
import re
import logging
from datetime import datetime
import traceback

# ========== 依赖自动安装 ==========
REQUIRED_PACKAGES = ['requests', 'keyring']
for pkg in REQUIRED_PACKAGES:
    try:
        __import__(pkg)
    except ImportError:
        print(f"⏳ 正在自动安装 {pkg} ...")
        try:
            subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])
            print(f"✅ {pkg} 安装成功")
        except Exception as e:
            print(f"❌ 自动安装 {pkg} 失败，请手动安装: pip install {pkg}")
            sys.exit(1)

import requests
import keyring

# ========== 日志配置 ==========
LOG_DIR = r"C:\TarjanOJ\logs"
LOG_FILE = os.path.join(LOG_DIR, f"checkin_{datetime.now().strftime('%Y-%m-%d')}.log")

def setup_logging():
    global LOG_DIR, LOG_FILE
    try:
        os.makedirs(LOG_DIR, exist_ok=True)
    except PermissionError:
        print(f"❌ 无法创建日志目录 {LOG_DIR}，请以管理员身份运行此脚本。")
        fallback_dir = os.path.join(os.getcwd(), "logs")
        os.makedirs(fallback_dir, exist_ok=True)
        LOG_DIR = fallback_dir
        LOG_FILE = os.path.join(LOG_DIR, f"checkin_{datetime.now().strftime('%Y-%m-%d')}.log")
        print(f"⚠️ 将在当前目录下创建日志: {LOG_FILE}")

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    fh = logging.FileHandler(LOG_FILE, encoding='utf-8')
    fh.setLevel(logging.DEBUG)
    fh_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    fh.setFormatter(fh_formatter)
    logger.addHandler(fh)

    ch = logging.StreamHandler()
    ch.setLevel(logging.INFO)
    ch_formatter = logging.Formatter('%(message)s')
    ch.setFormatter(ch_formatter)
    logger.addHandler(ch)

    return logger

logger = setup_logging()

# ========== 常量 ==========
SERVICE_NAME = "TarjanOJ"
TASK_NAME = "TarjanOJ_AutoCheckin"

# ========== 凭据管理 ==========
def get_credentials():
    logger.debug("正在从凭据管理器读取账号信息...")
    username = keyring.get_password(SERVICE_NAME, "username")
    password = keyring.get_password(SERVICE_NAME, "password")
    if username and password:
        logger.debug(f"读取到用户名: {username}")
    else:
        logger.debug("未找到已保存的凭据")
    return username, password

def save_credentials(username, password):
    try:
        keyring.set_password(SERVICE_NAME, "username", username)
        keyring.set_password(SERVICE_NAME, "password", password)
        logger.info("✅ 凭据已保存到 Windows 凭据管理器。")
        return True
    except Exception as e:
        logger.error(f"保存凭据失败: {e}")
        return False

def delete_credentials():
    """删除无效凭据"""
    try:
        keyring.delete_password(SERVICE_NAME, "username")
        keyring.delete_password(SERVICE_NAME, "password")
        logger.info("已删除无效凭据")
    except:
        pass

# ========== 定时任务管理 ==========
def check_task_exists():
    logger.debug("正在检查计划任务是否存在...")
    try:
        result = subprocess.run(
            ["schtasks", "/query", "/tn", TASK_NAME],
            capture_output=True, text=True
        )
        return result.returncode == 0
    except Exception as e:
        logger.error(f"检查任务时出错: {e}")
        return False

def create_task(script_path):
    logger.info("正在创建计划任务...")
    python_path = sys.executable
    cmd = f'"{python_path}" "{script_path}"'
    create_cmd = [
        "schtasks", "/create", "/tn", TASK_NAME,
        "/tr", cmd,
        "/sc", "hourly", "/mo", "12",
        "/f"
    ]
    try:
        result = subprocess.run(create_cmd, capture_output=True, text=True, shell=True)
        if result.returncode == 0:
            logger.info("✅ 定时任务创建成功，将每 12 小时自动签到一次。")
            return True
        else:
            logger.error(f"❌ 创建定时任务失败，错误: {result.stderr}")
            if "拒绝访问" in result.stderr or "Access denied" in result.stderr:
                logger.error("请以管理员身份重新运行此脚本。")
            return False
    except Exception as e:
        logger.error(f"创建任务时发生异常: {e}")
        return False

# ========== 签到核心 ==========
def do_checkin(username, password):
    """执行登录并签到，返回 (是否成功, 签到结果描述)"""
    logger.info("开始执行签到...")
    session = requests.Session()
    login_url = "https://tarjanoj.com/d/top1/login"
    data = {"uname": username, "password": password}

    try:
        logger.debug("发送登录请求...")
        login_resp = session.post(login_url, data=data, allow_redirects=True, timeout=15)
        cookies = session.cookies.get_dict()
        if 'sid' not in cookies or 'sid.sig' not in cookies:
            logger.warning("登录后未获得有效的会话Cookie")
            logger.info("❌ 登录失败：未获取到会话 Cookie")
            return False, "登录失败（Cookie缺失）"

        logger.debug("登录成功，继续签到...")
        checkin_resp = session.get("https://tarjanoj.com/checkin", timeout=15)
        html = checkin_resp.text

        coin_match = re.search(r'今日掉落\s*(\d+)\s*金币', html)
        continuous_match = re.search(r'连续打卡\s*<span>\s*(\d+)\s*</span>\s*天', html)
        total_match = re.search(r'累计打卡\s*<span>\s*(\d+)\s*</span>\s*天', html)

        if coin_match:
            coins = int(coin_match.group(1))
            continuous = int(continuous_match.group(1)) if continuous_match else 0
            total = int(total_match.group(1)) if total_match else 0
            msg = f"🎉 获得 {coins} 金币 | 连续 {continuous} 天 | 累计 {total} 天"
            logger.info(msg)
            return True, msg
        else:
            logger.warning("未在响应中找到金币信息")
            logger.info("❌ 签到失败：可能今日已签或 Cookie 无效")
            return False, "签到失败（未检测到金币信息）"

    except requests.RequestException as e:
        logger.error(f"网络请求异常: {e}")
        return False, f"网络异常: {e}"
    except Exception as e:
        logger.error(f"签到过程中发生未知错误: {e}")
        logger.debug(traceback.format_exc())
        return False, f"未知错误: {e}"

# ========== 主程序 ==========
def main():
    auto_create_task = "--create-task" in sys.argv

    print("=" * 60)
    print(WATERMARK)
    print("=" * 60)
    print("🔒 所有数据仅在本地 Windows 凭据管理器存储，无泄漏风险")

    logger.info("=" * 60)
    logger.info("Tarjan OJ 自动签到工具 v2.0")
    logger.info(f"日志文件: {LOG_FILE}")
    logger.info("=" * 60)

    # ---------- 1. 获取并验证凭据（通过签到） ----------
    username, password = get_credentials()
    signed_in = False  # 标记是否已完成签到

    if username and password:
        logger.info(f"✅ 已从凭据管理器读取到账号：{username}")
        logger.info("正在验证凭据并尝试签到...")
        success, msg = do_checkin(username, password)
        if success:
            signed_in = True
            logger.info("✅ 凭据有效，签到已完成")
        else:
            logger.warning(f"⚠️ 已保存的凭据无效: {msg}")
            delete_credentials()
            username = password = None
            logger.info("已删除无效凭据，请重新输入")
    else:
        logger.info("未找到已保存的凭据")

    # 如果还没有有效凭据或签到未完成，循环输入并签到
    while not signed_in:
        logger.info("\n请输入您的 Tarjan OJ 账号和密码。")
        username_input = input("用户名: ").strip()
        if not username_input:
            logger.error("用户名不能为空，退出。")
            sys.exit(1)
        password_input = input("密码: ").strip()
        if not password_input:
            logger.error("密码不能为空，退出。")
            sys.exit(1)

        logger.info("正在验证并签到...")
        success, msg = do_checkin(username_input, password_input)
        if success:
            # 签到成功，保存凭据
            if save_credentials(username_input, password_input):
                logger.info("凭据已保存")
            else:
                logger.warning("凭据保存失败，但本次签到已完成")
            username = username_input
            password = password_input
            signed_in = True
            logger.info("✅ 签到成功！")
            break
        else:
            logger.error(f"❌ 签到失败: {msg}")
            retry = input("是否重新输入账号密码重试？(y/n): ").strip().lower()
            if retry != 'y':
                logger.info("退出程序。")
                sys.exit(0)
            # 继续循环

    # ---------- 2. 创建定时任务（凭据已验证通过） ----------
    task_exists = check_task_exists()
    if not task_exists:
        if auto_create_task:
            logger.info("管理员模式，自动创建定时任务...")
            script_path = os.path.abspath(__file__)
            create_task(script_path)
        else:
            logger.info("未检测到自动签到定时任务。")
            resp = input("是否创建每 12 小时自动签到任务？(y/n): ").strip().lower()
            if resp == 'y':
                try:
                    is_admin = os.getuid() == 0
                except AttributeError:
                    import ctypes
                    is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0

                if not is_admin:
                    logger.warning("创建定时任务需要管理员权限。")
                    retry = input("是否尝试以管理员身份重新运行脚本？(y/n): ").strip().lower()
                    if retry == 'y':
                        try:
                            args = sys.argv + ["--create-task"]
                            params = subprocess.list2cmdline(args)
                            ctypes.windll.shell32.ShellExecuteW(
                                None, "runas", sys.executable, params, None, 1
                            )
                            logger.info("已以管理员身份重新启动，请在新的窗口中继续操作。")
                            sys.exit(0)
                        except Exception as e:
                            logger.error(f"提权失败: {e}")
                            logger.info("请手动以管理员身份重新运行脚本。")
                    else:
                        logger.info("跳过创建定时任务。")
                else:
                    script_path = os.path.abspath(__file__)
                    if create_task(script_path):
                        logger.info("定时任务已就绪。")
                    else:
                        logger.warning("未成功创建定时任务。")
            else:
                logger.info("跳过创建定时任务。")
    else:
        logger.info("✅ 已存在定时任务，将自动每 12 小时签到。")

    # ---------- 3. 若尚未签到（理论上不会发生）则补充签到 ----------
    if not signed_in:
        # 实际上凭据已验证并签到，此处为防御
        logger.info("执行最终签到...")
        do_checkin(username, password)

    logger.info("\n脚本执行完毕。")
    logger.info(f"详细日志已保存至: {LOG_FILE}")

if __name__ == "__main__":
    main()