"""
学生证件生成器 - 基于PyQt5 + Pillow
功能：输入学生信息、上传照片、生成并保存学生证图片
"""

import sys
import os
from datetime import datetime

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QLineEdit, QComboBox, QPushButton, QFileDialog,
    QMessageBox, QGroupBox, QGridLayout, QDateEdit, QSpinBox,
    QScrollArea, QFrame, QSizePolicy
)
from PyQt5.QtCore import Qt, QDate, QByteArray, QBuffer
from PyQt5.QtGui import QPixmap, QImage, QFont, QIcon

from PIL import Image, ImageDraw, ImageFont, ImageFilter

# ============================================================
# 配置区 - 可根据需要修改
# ============================================================
SCHOOL_NAME = "阳光科技大学"          # 学校名称
SCHOOL_MOTTO = "厚德 博学 求实 创新"  # 校训
CARD_WIDTH = 540                      # 学生证宽度(px)
CARD_HEIGHT = 760                     # 学生证高度(px)
BG_COLOR_TOP = (26, 82, 118)          # 背景渐变顶部颜色
BG_COLOR_BOTTOM = (41, 128, 185)      # 背景渐变底部颜色
TEXT_COLOR = (255, 255, 255)          # 文字颜色
ACCENT_COLOR = (243, 156, 18)         # 强调色（金色）
PHOTO_SIZE = (160, 200)               # 照片区域尺寸 (宽, 高)
PHOTO_POS = (190, 180)                # 照片左上角坐标


def find_chinese_font():
    """查找系统中可用的中文字体"""
    font_paths = [
        # Windows
        "C:/Windows/Fonts/simhei.ttf",
        "C:/Windows/Fonts/msyh.ttc",
        "C:/Windows/Fonts/msyhbd.ttc",
        "C:/Windows/Fonts/simsun.ttc",
        "C:/Windows/Fonts/fangsong.ttf",
        # macOS
        "/System/Library/Fonts/PingFang.ttc",
        "/System/Library/Fonts/STHeiti Light.ttc",
        "/System/Library/Fonts/STHeiti Medium.ttc",
        # Linux
        "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
        "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
        "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
    ]
    for path in font_paths:
        if os.path.exists(path):
            return path
    # 如果都没有，返回None，后面会用默认字体
    return None


FONT_PATH = find_chinese_font()


def load_font(font_path, size):
    """加载字体，失败时使用默认字体"""
    try:
        if font_path and os.path.exists(font_path):
            return ImageFont.truetype(font_path, size)
        else:
            return ImageFont.load_default()
    except Exception:
        return ImageFont.load_default()


# ============================================================
# 学生证生成引擎 (Pillow)
# ============================================================

class StudentCardRenderer:
    """使用Pillow渲染学生证图片"""

    @staticmethod
    def create_gradient_background(width, height, color_top, color_bottom):
        """创建渐变背景"""
        image = Image.new("RGB", (width, height), color_top)
        draw = ImageDraw.Draw(image)
        for y in range(height):
            ratio = y / height
            r = int(color_top[0] * (1 - ratio) + color_bottom[0] * ratio)
            g = int(color_top[1] * (1 - ratio) + color_bottom[1] * ratio)
            b = int(color_top[2] * (1 - ratio) + color_bottom[2] * ratio)
            draw.line([(0, y), (width, y)], fill=(r, g, b))
        return image

    @staticmethod
    def make_circular_mask(size):
        """创建圆形蒙版"""
        mask = Image.new("L", size, 0)
        draw = ImageDraw.Draw(mask)
        draw.ellipse((0, 0, size[0], size[1]), fill=255)
        return mask

    @staticmethod
    def process_photo(photo_pixmap, target_size):
        """将QPixmap照片处理为适合学生证的圆形图片"""
        # 转换为PIL Image
        qimage = photo_pixmap.toImage()
        buffer = QByteArray()
        qbuffer = QBuffer(buffer)
        qimage.save(qbuffer, "PNG")
        pil_image = Image.open(io.BytesIO(buffer.data()))

        # 裁剪为正方形（取中心）
        w, h = pil_image.size
        side = min(w, h)
        left = (w - side) // 2
        top = (h - side) // 2
        pil_image = pil_image.crop((left, top, left + side, top + side))

        # 缩放到目标尺寸
        pil_image = pil_image.resize(target_size, Image.LANCZOS)

        # 应用圆形蒙版
        mask = StudentCardRenderer.make_circular_mask(target_size)
        pil_image.putalpha(mask)

        return pil_image

    @staticmethod
    def generate_card(info, photo_pixmap=None):
        """
        生成学生证图片
        info: dict, 包含字段: name, gender, student_id, class_name, college, major, enrollment_year, birth_date
        photo_pixmap: QPixmap, 学生照片
        """
        width, height = CARD_WIDTH, CARD_HEIGHT

        # 1. 创建渐变背景
        card = StudentCardRenderer.create_gradient_background(
            width, height, BG_COLOR_TOP, BG_COLOR_BOTTOM
        )
        draw = ImageDraw.Draw(card)

        # 2. 加载字体
        font_large = load_font(FONT_PATH, 42)
        font_title = load_font(FONT_PATH, 28)
        font_info = load_font(FONT_PATH, 22)
        font_small = load_font(FONT_PATH, 16)
        font_motto = load_font(FONT_PATH, 20)

        # 3. 绘制顶部装饰条
        for y in range(0, 12):
            draw.line([(0, y), (width, y)], fill=(255, 215, 0, 150))

        # 4. 绘制学校名称
        school_text = SCHOOL_NAME
        # 居中对齐
        bbox = draw.textbbox((0, 0), school_text, font=font_large)
        tw = bbox[2] - bbox[0]
        tx = (width - tw) // 2
        draw.text((tx, 30), school_text, fill=TEXT_COLOR, font=font_large)

        # 5. 绘制校训
        motto_text = SCHOOL_MOTTO
        bbox = draw.textbbox((0, 0), motto_text, font=font_motto)
        mw = bbox[2] - bbox[0]
        mx = (width - mw) // 2
        draw.text((mx, 88), motto_text, fill=ACCENT_COLOR, font=font_motto)

        # 6. 绘制分隔线
        line_y = 125
        draw.line([(60, line_y), (width - 60, line_y)], fill=TEXT_COLOR, width=2)

        # 7. 绘制照片（圆形）
        photo_x, photo_y = PHOTO_POS
        photo_w, photo_h = PHOTO_SIZE

        # 绘制照片外圈（金色边框）
        border = 6
        draw.ellipse(
            [(photo_x - border, photo_y - border),
             (photo_x + photo_w + border, photo_y + photo_h + border)],
            outline=ACCENT_COLOR, width=border
        )

        # 绘制照片底片（浅色占位）
        draw.ellipse(
            [(photo_x, photo_y), (photo_x + photo_w, photo_y + photo_h)],
            fill=(200, 220, 240, 100), outline=None
        )

        # 如果有照片，粘贴上去
        if photo_pixmap and not photo_pixmap.isNull():
            try:
                circular_photo = StudentCardRenderer.process_photo(
                    photo_pixmap, (photo_w, photo_h)
                )
                card.paste(circular_photo, (photo_x, photo_y), circular_photo)
            except Exception as e:
                print(f"照片处理出错: {e}")

        # 8. 绘制学生信息（右半部分）
        info_x = photo_x + photo_w + 35
        info_start_y = photo_y + 5
        line_spacing = 38

        # 姓名 - 加大加粗
        name_text = f"{info.get('name', '未知')}"
        font_name = load_font(FONT_PATH, 32)
        draw.text((info_x, info_start_y), f"姓名: {name_text}", fill=TEXT_COLOR, font=font_name)

        # 其他信息
        fields = [
            ("性  别", info.get("gender", "")),
            ("学  号", info.get("student_id", "")),
            ("班  级", info.get("class_name", "")),
            ("学  院", info.get("college", "")),
            ("专  业", info.get("major", "")),
            ("入学年份", str(info.get("enrollment_year", ""))),
            ("出生日期", info.get("birth_date", "")),
        ]

        current_y = info_start_y + 55
        for label, value in fields:
            text = f"{label}: {value}"
            draw.text((info_x, current_y), text, fill=TEXT_COLOR, font=font_info)
            current_y += line_spacing

        # 9. 底部信息
        bottom_y = height - 50
        # 学号条形码效果（装饰）
        bar_code_text = f"ID: {info.get('student_id', '0000000000')}"
        bbox = draw.textbbox((0, 0), bar_code_text, font=font_small)
        bw = bbox[2] - bbox[0]
        draw.text(((width - bw) // 2, bottom_y), bar_code_text, fill=TEXT_COLOR, font=font_small)

        # 有效期
        valid_text = f"有效期至: {datetime.now().year + 4}-{datetime.now().month:02d}-{datetime.now().day:02d}"
        bbox = draw.textbbox((0, 0), valid_text, font=font_small)
        vw = bbox[2] - bbox[0]
        draw.text(((width - vw) // 2, bottom_y + 25), valid_text, fill=(200, 225, 245), font=font_small)

        # 10. 右下角防伪装饰
        draw.text((width - 80, height - 70), "★", fill=ACCENT_COLOR, font=load_font(FONT_PATH, 24))

        # 11. 左侧装饰线
        for i in range(0, height, 15):
            draw.point((15, i), fill=(255, 255, 255, 40))

        return card


import io  # 用于内存中的字节流操作


# ============================================================
# PyQt5 主界面
# ============================================================

class StudentIDGenerator(QMainWindow):
    """学生证生成器主窗口"""

    def __init__(self):
        super().__init__()
        self.photo_pixmap = None
        self.generated_card = None
        self.current_card_pixmap = None
        self.initUI()

    def initUI(self):
        self.setWindowTitle("学生证件生成器 v2.0")
        self.setMinimumSize(1100, 750)
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f0f4f8;
            }
            QGroupBox {
                font-size: 15px;
                font-weight: bold;
                border: 2px solid #bdc3c7;
                border-radius: 8px;
                margin-top: 12px;
                padding-top: 18px;
                background-color: white;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 15px;
                padding: 0 8px;
                color: #2c3e50;
            }
            QLabel {
                font-size: 14px;
                color: #34495e;
            }
            QLineEdit, QComboBox, QSpinBox, QDateEdit {
                padding: 6px 10px;
                border: 1.5px solid #ccd1d9;
                border-radius: 5px;
                font-size: 13px;
                background-color: #fafafa;
            }
            QLineEdit:focus, QComboBox:focus, QSpinBox:focus, QDateEdit:focus {
                border-color: #3498db;
                background-color: white;
            }
            QPushButton {
                padding: 8px 18px;
                border-radius: 6px;
                font-size: 14px;
                font-weight: bold;
                border: none;
            }
            QPushButton:hover {
                opacity: 0.9;
            }
        """)

        # 中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QHBoxLayout(central_widget)
        main_layout.setSpacing(20)
        main_layout.setContentsMargins(20, 15, 20, 15)

        # ========== 左侧：表单区域 ==========
        left_panel = QWidget()
        left_panel.setFixedWidth(420)
        left_layout = QVBoxLayout(left_panel)
        left_layout.setSpacing(8)

        # 标题
        title_label = QLabel("📋 学生信息录入")
        title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #2c3e50; padding: 5px 0;")
        left_layout.addWidget(title_label)

        # 基本信息组
        info_group = QGroupBox("基本信息")
        info_grid = QGridLayout()
        info_grid.setSpacing(10)

        # 姓名
        info_grid.addWidget(QLabel("姓  名:"), 0, 0)
        self.name_edit = QLineEdit()
        self.name_edit.setPlaceholderText("请输入姓名")
        info_grid.addWidget(self.name_edit, 0, 1)

        # 性别
        info_grid.addWidget(QLabel("性  别:"), 1, 0)
        self.gender_combo = QComboBox()
        self.gender_combo.addItems(["男", "女"])
        info_grid.addWidget(self.gender_combo, 1, 1)

        # 学号
        info_grid.addWidget(QLabel("学  号:"), 2, 0)
        self.student_id_edit = QLineEdit()
        self.student_id_edit.setPlaceholderText("请输入学号")
        info_grid.addWidget(self.student_id_edit, 2, 1)

        # 班级
        info_grid.addWidget(QLabel("班  级:"), 3, 0)
        self.class_edit = QLineEdit()
        self.class_edit.setPlaceholderText("例如: 计科2101班")
        info_grid.addWidget(self.class_edit, 3, 1)

        # 学院
        info_grid.addWidget(QLabel("学  院:"), 4, 0)
        self.college_combo = QComboBox()
        colleges = ["计算机科学与技术学院", "电子信息工程学院", "机械工程学院",
                     "土木建筑学院", "经济管理学院", "外国语学院", "理学院",
                     "马克思主义学院", "体育学院", "艺术设计学院"]
        self.college_combo.addItems(colleges)
        self.college_combo.setEditable(True)
        info_grid.addWidget(self.college_combo, 4, 1)

        # 专业
        info_grid.addWidget(QLabel("专  业:"), 5, 0)
        self.major_edit = QLineEdit()
        self.major_edit.setPlaceholderText("请输入专业名称")
        info_grid.addWidget(self.major_edit, 5, 1)

        # 入学年份
        info_grid.addWidget(QLabel("入学年份:"), 6, 0)
        self.year_spin = QSpinBox()
        current_year = datetime.now().year
        self.year_spin.setRange(current_year - 10, current_year + 2)
        self.year_spin.setValue(current_year)
        info_grid.addWidget(self.year_spin, 6, 1)

        # 出生日期
        info_grid.addWidget(QLabel("出生日期:"), 7, 0)
        self.birth_date = QDateEdit()
        self.birth_date.setCalendarPopup(True)
        self.birth_date.setDate(QDate(2000, 1, 1))
        self.birth_date.setDisplayFormat("yyyy-MM-dd")
        info_grid.addWidget(self.birth_date, 7, 1)

        info_group.setLayout(info_grid)
        left_layout.addWidget(info_group)

        # 照片上传组
        photo_group = QGroupBox("学生照片")
        photo_layout = QVBoxLayout()

        photo_btn_row = QHBoxLayout()
        self.upload_btn = QPushButton("📷 上传照片")
        self.upload_btn.setStyleSheet("background-color: #3498db; color: white; padding: 8px 20px;")
        self.upload_btn.clicked.connect(self.upload_photo)
        photo_btn_row.addWidget(self.upload_btn)

        self.clear_photo_btn = QPushButton("✖ 清除")
        self.clear_photo_btn.setStyleSheet("background-color: #e74c3c; color: white; padding: 8px 20px;")
        self.clear_photo_btn.clicked.connect(self.clear_photo)
        self.clear_photo_btn.setEnabled(False)
        photo_btn_row.addWidget(self.clear_photo_btn)
        photo_btn_row.addStretch()
        photo_layout.addLayout(photo_btn_row)

        # 照片预览缩略图
        self.photo_preview = QLabel()
        self.photo_preview.setFixedSize(120, 140)
        self.photo_preview.setAlignment(Qt.AlignCenter)
        self.photo_preview.setStyleSheet("""
            border: 2px dashed #bdc3c7;
            border-radius: 8px;
            background-color: #ecf0f1;
            font-size: 12px;
            color: #95a5a6;
        """)
        self.photo_preview.setText("点击上方\n上传照片")
        photo_layout.addWidget(self.photo_preview, alignment=Qt.AlignCenter)

        photo_group.setLayout(photo_layout)
        left_layout.addWidget(photo_group)

        # 操作按钮
        btn_layout = QHBoxLayout()
        self.generate_btn = QPushButton("🎓 生成学生证")
        self.generate_btn.setStyleSheet("""
            background-color: #27ae60; color: white; font-size: 16px;
            padding: 12px 30px; border-radius: 8px;
        """)
        self.generate_btn.clicked.connect(self.generate_card)
        btn_layout.addWidget(self.generate_btn)

        self.save_btn = QPushButton("💾 保存图片")
        self.save_btn.setStyleSheet("""
            background-color: #8e44ad; color: white; font-size: 16px;
            padding: 12px 30px; border-radius: 8px;
        """)
        self.save_btn.clicked.connect(self.save_card)
        self.save_btn.setEnabled(False)
        btn_layout.addWidget(self.save_btn)

        left_layout.addLayout(btn_layout)
        left_layout.addStretch()

        # ========== 右侧：预览区域 ==========
        right_panel = QWidget()
        right_layout = QVBoxLayout(right_panel)

        preview_title = QLabel("👀 学生证预览")
        preview_title.setStyleSheet("font-size: 20px; font-weight: bold; color: #2c3e50; padding: 5px 0;")
        right_layout.addWidget(preview_title)

        # 滚动区域包裹预览
        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        scroll.setStyleSheet("border: none; background: transparent;")

        preview_container = QWidget()
        preview_container.setStyleSheet("background: transparent;")
        preview_layout = QVBoxLayout(preview_container)

        self.card_display = QLabel()
        self.card_display.setAlignment(Qt.AlignCenter)
        self.card_display.setMinimumSize(CARD_WIDTH + 40, CARD_HEIGHT + 40)
        self.card_display.setStyleSheet("""
            background-color: white;
            border: 2px solid #dfe6e9;
            border-radius: 12px;
            padding: 15px;
        """)
        self.card_display.setText("📌 填写左侧信息后\n点击「生成学生证」")
        self.card_display.setStyleSheet(self.card_display.styleSheet() +
                                        "font-size: 18px; color: #b2bec3;")

        preview_layout.addWidget(self.card_display, alignment=Qt.AlignCenter)
        scroll.setWidget(preview_container)
        right_layout.addWidget(scroll)

        # 添加到主布局
        main_layout.addWidget(left_panel)
        main_layout.addWidget(right_panel, 1)

        # 状态栏
        self.statusBar().showMessage("就绪 | 请填写学生信息并上传照片")

    # ---------- 槽函数 ----------

    def upload_photo(self):
        """上传照片"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择学生照片", "",
            "图片文件 (*.jpg *.jpeg *.png *.bmp)"
        )
        if file_path:
            pixmap = QPixmap(file_path)
            if pixmap.isNull():
                QMessageBox.warning(self, "提示", "无法加载图片，请检查文件格式。")
                return
            # 缩放预览缩略图
            scaled = pixmap.scaled(
                115, 135, Qt.KeepAspectRatio, Qt.SmoothTransformation
            )
            self.photo_preview.setPixmap(scaled)
            self.photo_preview.setStyleSheet("border: 2px solid #27ae60; border-radius: 8px;")
            self.photo_pixmap = pixmap
            self.clear_photo_btn.setEnabled(True)
            self.statusBar().showMessage(f"✅ 已加载照片: {os.path.basename(file_path)}")

    def clear_photo(self):
        """清除照片"""
        self.photo_pixmap = None
        self.photo_preview.clear()
        self.photo_preview.setText("点击上方\n上传照片")
        self.photo_preview.setStyleSheet("""
            border: 2px dashed #bdc3c7;
            border-radius: 8px;
            background-color: #ecf0f1;
            font-size: 12px;
            color: #95a5a6;
        """)
        self.clear_photo_btn.setEnabled(False)
        self.statusBar().showMessage("已清除照片")

    def collect_info(self):
        """收集表单信息"""
        info = {
            "name": self.name_edit.text().strip(),
            "gender": self.gender_combo.currentText(),
            "student_id": self.student_id_edit.text().strip(),
            "class_name": self.class_edit.text().strip(),
            "college": self.college_combo.currentText().strip(),
            "major": self.major_edit.text().strip(),
            "enrollment_year": self.year_spin.value(),
            "birth_date": self.birth_date.date().toString("yyyy-MM-dd"),
        }
        return info

    def validate_info(self, info):
        """验证信息完整性"""
        if not info["name"]:
            QMessageBox.warning(self, "提示", "请输入学生姓名！")
            self.name_edit.setFocus()
            return False
        if not info["student_id"]:
            QMessageBox.warning(self, "提示", "请输入学号！")
            self.student_id_edit.setFocus()
            return False
        if not info["class_name"]:
            QMessageBox.warning(self, "提示", "请输入班级！")
            self.class_edit.setFocus()
            return False
        if not info["major"]:
            QMessageBox.warning(self, "提示", "请输入专业！")
            self.major_edit.setFocus()
            return False
        return True

    def generate_card(self):
        """生成学生证"""
        info = self.collect_info()
        if not self.validate_info(info):
            return

        try:
            self.statusBar().showMessage("⏳ 正在生成学生证...")
            QApplication.processEvents()

            # 调用渲染引擎生成学生证
            card_image = StudentCardRenderer.generate_card(info, self.photo_pixmap)

            # 转换为QPixmap显示
            img_byte_arr = io.BytesIO()
            card_image.save(img_byte_arr, format="PNG")
            img_byte_arr.seek(0)

            qimage = QImage.fromData(img_byte_arr.getvalue(), "PNG")
            if qimage.isNull():
                raise Exception("图片转换失败")

            pixmap = QPixmap.fromImage(qimage)

            # 缩放显示（适配预览区域）
            display_size = self.card_display.size()
            display_width = max(display_size.width() - 30, 300)
            display_height = max(display_size.height() - 30, 450)

            # 按比例缩放
            scaled = pixmap.scaled(
                display_width, display_height,
                Qt.KeepAspectRatio, Qt.SmoothTransformation
            )
            self.card_display.setPixmap(scaled)
            self.card_display.setStyleSheet("""
                background-color: white;
                border: 2px solid #27ae60;
                border-radius: 12px;
                padding: 15px;
            """)

            # 保存原图供后续保存
            self.generated_card = card_image
            self.current_card_pixmap = pixmap
            self.save_btn.setEnabled(True)

            self.statusBar().showMessage(f"✅ 学生证生成成功！({info['name']})")

        except Exception as e:
            QMessageBox.critical(self, "生成失败", f"生成学生证时发生错误:\n{str(e)}")
            self.statusBar().showMessage("❌ 生成失败，请检查信息")

    def save_card(self):
        """保存学生证图片"""
        if self.generated_card is None:
            QMessageBox.warning(self, "提示", "请先生成学生证！")
            return

        # 获取学生姓名作为文件名
        name = self.name_edit.text().strip() or "学生证"
        default_name = f"{name}_学生证.png"

        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存学生证",
            default_name,
            "PNG图片 (*.png);;JPEG图片 (*.jpg *.jpeg);;所有文件 (*.*)"
        )
        if file_path:
            try:
                # 根据扩展名选择格式
                ext = os.path.splitext(file_path)[1].lower()
                fmt = "JPEG" if ext in (".jpg", ".jpeg") else "PNG"
                self.generated_card.save(file_path, format=fmt)
                QMessageBox.information(self, "保存成功",
                                        f"学生证已保存至:\n{file_path}")
                self.statusBar().showMessage(f"💾 已保存: {file_path}")
            except Exception as e:
                QMessageBox.critical(self, "保存失败", f"保存图片时出错:\n{str(e)}")


# ============================================================
# 程序入口
# ============================================================

def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")

    # 全局字体
    font = QFont("微软雅黑", 10)
    if not font.exactMatch():
        font = QFont("Segoe UI", 10)
    app.setFont(font)

    window = StudentIDGenerator()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()