from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import os
import sys
import tempfile
import logging
from datetime import datetime
import random
import hashlib
import secrets
from functools import wraps
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from PIL import Image
import pandas as pd
import io
import base64

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)
CORS(app)  # 允许跨域请求

# ==================== 安全配置 ====================
class SecurityConfig:
    def __init__(self):
        # 默认API密钥（部署时可修改）
        self.API_KEY = os.environ.get('CERTIFICATE_API_KEY', 'cert2024!@#')
        
        # 密钥哈希（用于验证）
        self.API_KEY_HASH = '75f4116161479933cf85b30bd37080b1f22168de9057da5ac934cc5aee8c59e7'
        
        # 会话管理
        self.active_sessions = {}
        
        # 访问日志
        self.access_log = []
        
        logger.info("安全模块已初始化")
    
    def _hash_key(self, key):
        """对密钥进行哈希处理"""
        return hashlib.sha256(key.encode()).hexdigest()
    
    def verify_api_key(self, provided_key):
        """验证API密钥"""
        if not provided_key:
            return False
        return self._hash_key(provided_key) == self.API_KEY_HASH
    
    def generate_session_token(self):
        """生成会话令牌"""
        return secrets.token_urlsafe(32)
    
    def log_access(self, ip, endpoint, success=True):
        """记录访问日志"""
        log_entry = {
            'timestamp': datetime.now().isoformat(),
            'ip': ip,
            'endpoint': endpoint,
            'success': success
        }
        self.access_log.append(log_entry)
        
        # 保留最近1000条记录
        if len(self.access_log) > 1000:
            self.access_log = self.access_log[-1000:]

# 创建安全配置实例
security = SecurityConfig()

# ==================== 认证装饰器 ====================
def require_auth(f):
    """API密钥认证装饰器"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        client_ip = request.remote_addr
        
        # 检查Authorization header
        auth_header = request.headers.get('Authorization')
        api_key = None
        
        if auth_header and auth_header.startswith('Bearer '):
            api_key = auth_header[7:]  # 移除 "Bearer " 前缀
        else:
            # 也支持从JSON body中获取API密钥
            if request.is_json:
                data = request.get_json()
                api_key = data.get('api_key') if data else None
            # 也支持从URL参数获取
            if not api_key:
                api_key = request.args.get('api_key')
        
        # 验证API密钥
        if not security.verify_api_key(api_key):
            security.log_access(client_ip, request.endpoint, success=False)
            return jsonify({
                "error": "无效的API密钥",
                "message": "请提供有效的API密钥进行访问",
                "code": 401
            }), 401
        
        # 记录成功访问
        security.log_access(client_ip, request.endpoint, success=True)
        
        return f(*args, **kwargs)
    return decorated_function

# ==================== 原有的证书生成类 ====================
class CertificateAPIGenerator:
    def __init__(self):
        # 获取程序运行目录
        if getattr(sys, 'frozen', False):
            self.base_path = os.path.dirname(os.path.abspath(sys.executable))
        else:
            self.base_path = os.path.dirname(os.path.abspath(__file__))
            
        # 设置模板和字体目录
        self.templates_dir = os.path.join(self.base_path, "templates")
        self.fonts_dir = os.path.join(self.base_path, "fonts")
        
        # 确保必要的目录存在
        os.makedirs(self.templates_dir, exist_ok=True)
        os.makedirs(self.fonts_dir, exist_ok=True)
        
        # 默认配置 - 与原始GUI版本保持一致
        self.default_template = "dxai.jpg"
        self.default_font = "yumindb"
        self.default_font_size = 133  # 与原始代码一致
        self.default_name_y = 1320    # 与原始代码一致
        self.default_furigana_y = 1220  # 在名字上方
        self.default_course = "AIデータアナリスト"
        
        # 其他坐标设置 - 与原始代码一致
        self.default_name_x = 2200
        self.default_number_x = 330
        self.default_number_y = 400
        self.default_date_x = 330
        self.default_date_y = 340
        
        # 可用的课程列表 - 与原始代码一致
        self.available_courses = [
            "AIプログラマー養成コース",
            "AIサーバー構築基礎コース", 
            "AIデータアナリストコース",
            "AIプログラミング実戦",
            "AIデータ分析基礎コース",
            "AIデータサイエンス基礎コース",
            "生成AI（活用、ChatGPT、Copilot）コース",
            "生成AI（活用、ChatGPT、Copilot）講座",
            "AIサーバーのDX化活用基礎コース",
            "AIデータサイエンティスト育成コース"
        ]
        
        # 可用的字体列表
        self.available_fonts = ["yumindb", "msgothic", "msmincho"]
        
    def create_certificate(self, name, output_path=None, **kwargs):
        """
        生成证书的核心函数 - 完全按照原始GUI版本的逻辑
        """
        try:
            # 参数处理
            template = kwargs.get('template', self.default_template)
            font = kwargs.get('font', self.default_font)
            font_size = int(kwargs.get('font_size', self.default_font_size))
            furigana = kwargs.get('furigana', '').strip()
            course = kwargs.get('course', self.default_course)
            name_y = float(kwargs.get('name_y', self.default_name_y))
            furigana_y = float(kwargs.get('furigana_y', self.default_furigana_y))
            
            # 验证参数
            if not name or not name.strip():
                raise ValueError("姓名不能为空")
                
            # 只删除开头和结尾的空格，保留中间的空格，并添加"殿"字
            name = name.strip()
            name = name + "　殿"
            
            # 检查模板文件
            template_path = os.path.join(self.templates_dir, template)
            if not os.path.exists(template_path):
                raise FileNotFoundError(f"模板文件不存在: {template}")
            
            # 获取图片尺寸
            img = Image.open(template_path)
            width, height = img.size
            
            # 创建临时文件或使用指定路径
            if output_path is None:
                temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
                output_path = temp_file.name
                temp_file.close()
            
            # 创建PDF
            c = canvas.Canvas(output_path, pagesize=(width, height))
            c.setPageSize((width, height))
            
            # 加载字体 - 按原始代码逻辑
            self._load_fonts_original(font)
            
            # 使用原始尺寸绘制图片
            c.drawImage(
                template_path, 
                0, 0, 
                width, height,
                preserveAspectRatio=True,
                anchor='c'
            )
            
            # 按原始代码逻辑绘制所有元素
            self._draw_certificate_original(c, name, font, font_size, furigana, course, width, height, name_y)
            
            c.save()
            
            return output_path
            
        except Exception as e:
            logger.error(f"生成证书时出错: {str(e)}")
            raise
    
    def _load_fonts_original(self, font):
        """按原始代码逻辑加载字体文件"""
        try:
            # 检查fonts文件夹是否存在
            if not os.path.exists(self.fonts_dir):
                raise Exception("找不到fonts文件夹，请确保fonts文件夹在程序目录中")
            
            # 加载默认字体（yumindb）
            default_font_path = os.path.join(self.fonts_dir, "yumindb.ttf")
            if os.path.exists(default_font_path):
                try:
                    pdfmetrics.registerFont(TTFont('yumindb', default_font_path))
                except:
                    raise Exception("默认字体文件加载失败，请确保yumindb.ttf文件完整")
            else:
                raise Exception("找不到默认字体文件yumindb.ttf，请确保字体文件在fonts文件夹中")
            
            # 加载选择的字体（用于姓名）
            if font != 'yumindb':  # 如果不是默认字体，才需要额外加载
                font_file = f"{font}.ttf"
                font_path = os.path.join(self.fonts_dir, font_file)
                if os.path.exists(font_path):
                    try:
                        pdfmetrics.registerFont(TTFont(font, font_path))
                    except:
                        raise Exception(f"字体文件 {font_file} 加载失败，请确保字体文件完整")
                else:
                    raise Exception(f"找不到字体文件 {font_file}，请确保字体文件在fonts文件夹中")
                    
        except Exception as e:
            logger.error(f"加载字体失败: {str(e)}")
            raise
    
    def _draw_certificate_original(self, c, name, selected_font, name_font_size, furigana, course_text, width, height, name_y):
        """按原始代码逻辑绘制整个证书"""
        # 计算文字总宽度（调整每个字符的宽度计算）
        total_width = 0
        for char in name:
            if ord(char) < 128:  # ASCII字符（英文、数字等）
                total_width += name_font_size * 0.5
            else:  # 中日韩文字
                total_width += name_font_size * 1.0  # 中日韩字符宽度
        
        # 计算居中位置（稍微向右偏移）
        center_x = (width / 2) + 20  # 将中心点向右偏移20个单位，与原代码一致
        adjusted_x = center_x - (total_width / 2)
        
        # 通过多次绘制实现加粗效果
        c.setFont(selected_font, name_font_size)
        
        # 减小offset值使字体看起来不那么黑
        offset = 0.4  # 与原代码一致
        
        # 绘制文字（保持加粗但减少重叠）
        c.drawString(adjusted_x-offset, name_y, name)  # 左
        c.drawString(adjusted_x+offset, name_y, name)  # 右
        c.drawString(adjusted_x, name_y, name)        # 中心
        
        # 如果有假名，绘制假名
        if furigana:
            # 设置假名字体大小
            furigana_size = int(name_font_size * 0.37)  # 从0.4改为0.35，使假名更小
            c.setFont('yumindb', furigana_size)  # 使用默认字体
            
            # 计算假名的位置（在主名字下方）
            furigana_width = c.stringWidth(furigana)
            furigana_x = center_x - (furigana_width / 2)
            furigana_y = name_y - 100
            
            # 设置假名加粗的偏移量
            offset_small = 0.3
            
            # 计算主名字的总宽度（不包括"殿"字）
            main_name = name.replace("　殿", "")
            
            # 分割姓和名
            name_parts = main_name.split()
            furigana_parts = furigana.split()
            
            if len(name_parts) == len(furigana_parts):
                current_x = adjusted_x
                
                for i, (name_part, furigana_part) in enumerate(zip(name_parts, furigana_parts)):
                    part_width = c.stringWidth(name_part, selected_font, name_font_size)
                    furigana_width = c.stringWidth(furigana_part, 'yumindb', furigana_size)
                    
                    furigana_x = current_x + ((part_width - furigana_width) / 2)
                    
                    c.drawString(furigana_x-offset_small, furigana_y, furigana_part)
                    c.drawString(furigana_x+offset_small, furigana_y, furigana_part)
                    c.drawString(furigana_x, furigana_y, furigana_part)
                    
                    if i < len(name_parts) - 1:
                        current_x += part_width + (name_font_size * 1.0)  # 这里控制姓名间距
        
        # 绘制コース文字
        c.setFillColorRGB(0.13, 0.19, 0.45)  # 设置海军蓝色
        
        # 设置コース字体大小
        course_font_size = int(name_font_size * 0.8)
        c.setFont('yumindb', course_font_size)  # 使用默认字体
        
        # 使用stringWidth准确计算コース文字宽度
        course_width = c.stringWidth(course_text)
        
        # 使用图片实际宽度来计算居中位置
        course_x = (width / 2) - (course_width / 2)  # 确保完全居中
        course_y = 950  # 垂直位置
        
        # 绘制コース文字（保持加粗效果）
        offset = 0.3
        c.drawString(course_x-offset, course_y, course_text)
        c.drawString(course_x+offset, course_y, course_text)
        c.drawString(course_x, course_y, course_text)
        
        # 重置为黑色用于其他文字
        c.setFillColorRGB(0, 0, 0)
        
        # 写入证书编号和日期
        c.setFont('yumindb', 60)  # 调整证书编号和日期的字体大小
        
        # 绘制证书编号
        number_x = self.default_number_x
        number_y = self.default_number_y
        certificate_number = self.generate_certificate_number(name)
        
        # 添加"証明書番号："标签并绘制
        cert_label = "証明書番号："
        c.drawString(number_x, number_y, cert_label + certificate_number)
        
        # 绘制日期
        date_x = self.default_date_x
        date_y = self.default_date_y
        current_date = datetime.now().strftime("%Y/%m/%d")
        
        # 添加"発行日："标签并绘制
        date_label = "発行日："
        c.drawString(date_x, date_y, date_label + current_date)
    
    def generate_certificate_number(self, name):
        """
        生成11位证书编号 - 与原始代码一致
        格式: YYMMDD + 5位哈希值
        """
        # 获取当前日期的6位数字 (年月日)
        date_str = datetime.now().strftime('%y%m%d')  # 例如: 240320
        
        # 计算名字的哈希值后5位
        name_hash = abs(hash(name)) % 100000  # 取哈希值的后5位
        name_hash_str = f"{name_hash:05d}"  # 补齐5位
        
        # 组合成11位证书编号
        certificate_number = f"{date_str}{name_hash_str}"
        
        return certificate_number

# 创建全局生成器实例
generator = CertificateAPIGenerator()

# ==================== API路由 ====================

@app.route('/', methods=['GET'])
def index():
    """API根路径，返回API信息和认证说明"""
    return jsonify({
        "name": "Certificate Generator API (Secure)",
        "version": "2.0.0",
        "description": "安全的PDF证书生成API - 需要API密钥认证",
        "authentication": {
            "type": "API Key",
            "methods": [
                "Authorization: Bearer <api_key>",
                "JSON body: {'api_key': '<api_key>'}",
                "URL parameter: ?api_key=<api_key>"
            ],
            "note": "所有API端点都需要有效的API密钥"
        },
        "endpoints": {
            "/auth": "POST - 验证API密钥",
            "/generate": "POST - 生成单个证书 (需要认证)",
            "/batch_generate": "POST - 批量生成证书 (需要认证)",
            "/templates": "GET - 列出可用模板 (需要认证)",
            "/fonts": "GET - 列出可用字体 (需要认证)",
            "/courses": "GET - 列出可用课程 (需要认证)",
            "/health": "GET - 健康检查 (无需认证)"
        }
    })

@app.route('/auth', methods=['POST'])
def authenticate():
    """验证API密钥"""
    try:
        data = request.get_json() if request.is_json else {}
        api_key = data.get('api_key') or request.args.get('api_key')
        
        if not api_key:
            return jsonify({
                "error": "缺少API密钥",
                "message": "请提供api_key参数"
            }), 400
        
        if security.verify_api_key(api_key):
            return jsonify({
                "success": True,
                "message": "API密钥验证成功",
                "timestamp": datetime.now().isoformat()
            })
        else:
            return jsonify({
                "success": False,
                "message": "无效的API密钥"
            }), 401
            
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/generate', methods=['POST'])
@require_auth
def generate_certificate():
    """生成单个证书的API端点 - 需要认证"""
    try:
        data = request.get_json()
        
        if not data:
            return jsonify({"error": "请提供JSON数据"}), 400
        
        name = data.get('name')
        if not name:
            return jsonify({"error": "姓名不能为空"}), 400
        
        # 可选参数 - 使用与原始GUI版本一致的默认值
        template = data.get('template', generator.default_template)
        font = data.get('font', generator.default_font)
        font_size = data.get('font_size', generator.default_font_size)
        furigana = data.get('furigana', '')
        course = data.get('course', generator.default_course)
        name_y = data.get('name_y', generator.default_name_y)
        furigana_y = data.get('furigana_y', generator.default_furigana_y)
        
        # 生成证书
        output_path = generator.create_certificate(
            name=name,
            template=template,
            font=font,
            font_size=font_size,
            furigana=furigana,
            course=course,
            name_y=name_y,
            furigana_y=furigana_y
        )
        
        # 返回PDF文件
        return send_file(
            output_path,
            as_attachment=True,
            download_name=f"{name.replace('　殿', '')}_证书.pdf",
            mimetype='application/pdf'
        )
        
    except Exception as e:
        logger.error(f"生成证书失败: {str(e)}")
        return jsonify({"error": str(e)}), 500

@app.route('/batch_generate', methods=['POST'])
@require_auth
def batch_generate_certificates():
    """批量生成证书的API端点 - 需要认证"""
    try:
        data = request.get_json()
        
        if not data:
            return jsonify({"error": "请提供JSON数据"}), 400
        
        names = data.get('names')
        if not names or not isinstance(names, list):
            return jsonify({"error": "请提供姓名列表"}), 400
        
        # 可选参数
        template = data.get('template', generator.default_template)
        font = data.get('font', generator.default_font)
        font_size = data.get('font_size', generator.default_font_size)
        course = data.get('course', generator.default_course)
        
        results = []
        success_count = 0
        
        for item in names:
            try:
                if isinstance(item, str):
                    name = item
                    furigana = ''
                    item_course = course
                elif isinstance(item, dict):
                    name = item.get('name', '')
                    furigana = item.get('furigana', '')
                    item_course = item.get('course', course)
                else:
                    results.append({"name": str(item), "status": "error", "message": "无效的数据格式"})
                    continue
                
                if not name:
                    results.append({"name": name, "status": "error", "message": "姓名不能为空"})
                    continue
                
                # 生成证书
                output_path = generator.create_certificate(
                    name=name,
                    template=template,
                    font=font,
                    font_size=font_size,
                    furigana=furigana,
                    course=item_course
                )
                
                # 读取文件内容并编码为base64
                with open(output_path, 'rb') as f:
                    pdf_content = base64.b64encode(f.read()).decode('utf-8')
                
                # 删除临时文件
                os.unlink(output_path)
                
                results.append({
                    "name": name,
                    "status": "success",
                    "pdf_base64": pdf_content,
                    "filename": f"{name.replace('　殿', '')}_证书.pdf"
                })
                success_count += 1
                
            except Exception as e:
                results.append({
                    "name": name if 'name' in locals() else "unknown",
                    "status": "error",
                    "message": str(e)
                })
        
        return jsonify({
            "total": len(names),
            "success": success_count,
            "failed": len(names) - success_count,
            "results": results
        })
        
    except Exception as e:
        logger.error(f"批量生成证书失败: {str(e)}")
        return jsonify({"error": str(e)}), 500

@app.route('/templates', methods=['GET'])
@require_auth
def list_templates():
    """列出可用的模板 - 需要认证"""
    try:
        templates = []
        for file in os.listdir(generator.templates_dir):
            if file.lower().endswith(('.jpg', '.jpeg', '.png')):
                templates.append(file)
        return jsonify({"templates": templates})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/fonts', methods=['GET'])
@require_auth
def list_fonts():
    """列出可用的字体 - 需要认证"""
    try:
        fonts = []
        for file in os.listdir(generator.fonts_dir):
            if file.lower().endswith('.ttf'):
                fonts.append(file.replace('.ttf', ''))
        return jsonify({"fonts": fonts})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/courses', methods=['GET'])
@require_auth
def list_courses():
    """列出可用的课程 - 需要认证"""
    return jsonify({"courses": generator.available_courses})

@app.route('/health', methods=['GET'])
def health_check():
    """健康检查端点 - 无需认证"""
    return jsonify({
        "status": "healthy", 
        "timestamp": datetime.now().isoformat(),
        "security": "enabled"
    })

@app.route('/admin/logs', methods=['GET'])
@require_auth
def get_access_logs():
    """获取访问日志 - 需要认证"""
    try:
        # 限制返回最近100条记录
        recent_logs = security.access_log[-100:]
        return jsonify({
            "total_logs": len(security.access_log),
            "recent_logs": recent_logs
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    print("=" * 60)
    print("🔒 安全证书生成API服务启动中...")
    print("=" * 60)
    print("安全特性:")
    print("- ✅ API密钥认证")
    print("- ✅ 访问日志记录")
    print("- ✅ 安全哈希验证")
    print("")
    print(f"默认API密钥: {security.API_KEY}")
    print("⚠️  部署到生产环境时请修改API密钥！")
    print("")
    print("API端点:")
    print("- GET  / : API信息")
    print("- POST /auth : 验证密钥")
    print("- POST /generate : 生成单个证书 (需要认证)")
    print("- POST /batch_generate : 批量生成证书 (需要认证)")
    print("- GET  /templates : 列出可用模板 (需要认证)")
    print("- GET  /fonts : 列出可用字体 (需要认证)")
    print("- GET  /courses : 列出可用课程 (需要认证)")
    print("- GET  /health : 健康检查")
    print("- GET  /admin/logs : 访问日志 (需要认证)")
    print("")
    print("服务地址: http://localhost:5000")
    print("=" * 60)
    
    app.run(debug=False, host='0.0.0.0', port=5000) 