# Source: https://github.com/CN-Grace/QinglongScripts/blob/main/SSL.py
# Raw: https://raw.githubusercontent.com/CN-Grace/QinglongScripts/main/SSL.py
# Repo: CN-Grace/QinglongScripts
# Path: SSL.py
# UploadedAt: 2026-05-23T16:04:54Z
# SHA256: c1f33bef544c897a6d3ea4c28fde98c6ee9729002ec45451f6197a1348d24589
# Category: APP版/抓包
# Evidence: cookie/token/authorization/header

#!/usr/bin/env python3
"""
cron: 0 0 * * *
new Env("SSL证书检查")
SSL 证书检查脚本
- 批量检查多个域名的 SSL 证书状态
- 获取证书到期时间、剩余天数、颁发者等信息
- 分类显示正常、警告、过期和检查失败的证书
"""

import os
import ssl
import socket
from datetime import datetime, timezone
from typing import List, Dict

from utils import log_info, log_success, log_warning, log_error, beijing_now, beijing_time_str
from notifier import send as notify_send

# ==================== 用户配置 ====================
DOMAINS_TO_CHECK = [d.strip() for d in os.environ.get("SSL_DOMAINS", "").split(",") if d.strip()]
WARNING_THRESHOLD = 30
CONNECTION_TIMEOUT = 10


def get_certificate_info(domain: str) -> Dict:
    """获取单个域名的 SSL 证书信息"""
    result = {"domain": domain, "expiry_date": datetime.min, "days_left": -1, "issuer": "", "is_valid": False, "error": None}
    try:
        hostname = domain if ":" in domain else f"{domain}:443"
        host, port = hostname.split(":")
        port = int(port)
        context = ssl.create_default_context()
        with socket.create_connection((host, port), timeout=CONNECTION_TIMEOUT) as sock:
            with context.wrap_socket(sock, server_hostname=host) as ssock:
                cert = ssock.getpeercert()

        expiry_str = cert["notAfter"]
        expiry_date = datetime.strptime(expiry_str, "%b %d %H:%M:%S %Y %Z").replace(tzinfo=timezone.utc)
        now = datetime.now(timezone.utc)
        days_left = (expiry_date - now).days
        issuer_dict = dict(x[0] for x in cert["issuer"])
        issuer = issuer_dict.get("organizationName", "Unknown")
        result.update({"expiry_date": expiry_date, "days_left": days_left, "issuer": issuer, "is_valid": days_left > 0})
        log_info(f"{domain} 证书剩余 {days_left} 天")
    except Exception as e:
        result["error"] = str(e)
        log_error(f"{domain} 检查失败: {e}")
    return result


def check_all_domains(domains: List[str]) -> List[Dict]:
    """检查所有域名的 SSL 证书"""
    results = []
    log_info(f"开始检查 {len(domains)} 个域名的 SSL 证书...")
    for domain in domains:
        log_info(f"正在检查: {domain}")
        cert_info = get_certificate_info(domain)
        results.append(cert_info)
        if cert_info["is_valid"]:
            if cert_info["days_left"] <= WARNING_THRESHOLD:
                log_warning(f"  ⚠️ {domain} 将在 {cert_info['days_left']} 天后过期")
            else:
                log_success(f"  ✅ {domain} 证书正常，剩余 {cert_info['days_left']} 天")
        elif cert_info["error"]:
            log_error(f"  🔧 {domain} 检查失败: {cert_info['error']}")
        else:
            log_error(f"  ❌ {domain} 证书已过期")
    return results


def categorize_certificates(certificates: List[Dict]) -> Dict[str, List[Dict]]:
    """将证书结果分类"""
    warning_certs, expired_certs, valid_certs, error_certs = [], [], [], []
    for cert in certificates:
        if cert["error"] is not None:
            error_certs.append(cert)
        elif cert["is_valid"]:
            if 0 < cert["days_left"] <= WARNING_THRESHOLD:
                warning_certs.append(cert)
            elif cert["days_left"] > WARNING_THRESHOLD:
                valid_certs.append(cert)
        else:
            expired_certs.append(cert)
    return {"warning": warning_certs, "expired": expired_certs, "valid": valid_certs, "error": error_certs}


def format_certificate_report(certificates: List[Dict]) -> str:
    """格式化证书检查报告"""
    cats = categorize_certificates(certificates)
    lines = [f"🔔 SSL 证书检查报告", "", f"⏰ 检查时间: {beijing_time_str()}", f"📊 总计: {len(certificates)} 个域名", ""]

    if cats["expired"]:
        lines.append("❌ 已过期的证书:")
        for cert in cats["expired"]:
            expiry = cert["expiry_date"].strftime("%Y-%m-%d") if cert["expiry_date"] != datetime.min else "未知"
            lines.append(f"   {cert['domain']} — 已过期 {abs(cert['days_left'])} 天 (到期: {expiry})")
        lines.append("")

    if cats["warning"]:
        lines.append("⚠️ 即将过期的证书 (30天内):")
        for cert in cats["warning"]:
            expiry = cert["expiry_date"].strftime("%Y-%m-%d")
            lines.append(f"   {cert['domain']} — 剩余 {cert['days_left']} 天 (到期: {expiry} | 颁发: {cert['issuer']})")
        lines.append("")

    if cats["valid"]:
        lines.append("✅ 证书状态正常:")
        for cert in cats["valid"]:
            lines.append(f"   {cert['domain']} — 剩余 {cert['days_left']} 天")
        lines.append("")

    if cats["error"]:
        lines.append("🔧 检查失败的域名:")
        for cert in cats["error"]:
            lines.append(f"   {cert['domain']} — 错误: {cert['error']}")
        lines.append("")

    lines.append("📈 统计信息:")
    lines.append(f"   ✅ 正常: {len(cats['valid'])}")
    lines.append(f"   ⚠️ 警告: {len(cats['warning'])}")
    lines.append(f"   ❌ 过期: {len(cats['expired'])}")
    lines.append(f"   🔧 失败: {len(cats['error'])}")
    lines.append("")
    lines.append("─" * 18)
    lines.append(f"🕒 执行时间: {beijing_time_str()}")
    return "\n".join(lines)


def main():
    log_info("=" * 50)
    log_info("SSL 证书检查脚本开始执行")
    log_info("=" * 50)

    cert_results = check_all_domains(DOMAINS_TO_CHECK)
    report = format_certificate_report(cert_results)
    notify_send("SSL 证书检查报告", report)

    cats = categorize_certificates(cert_results)
    print(f"\n{'=' * 60}")
    print(f"SSL 证书检查完成! 检查域名总数: {len(cert_results)}")
    print(f"正常: {len(cats['valid'])} | 警告: {len(cats['warning'])} | 过期: {len(cats['expired'])} | 失败: {len(cats['error'])}")
    print("=" * 60)

    if cats["expired"] or cats["warning"]:
        print("\n⚠️ 需要注意的域名:")
        for cert in cats["expired"]:
            print(f"  ❌ {cert['domain']} — 已过期")
        for cert in cats["warning"]:
            print(f"  ⚠️ {cert['domain']} — 剩余 {cert['days_left']} 天到期")

    log_info("=" * 50)
    log_info(f"脚本执行结束")
    log_info("=" * 50)


if __name__ == "__main__":
    main()
