import tkinter as tk
from tkinter import ttk, messagebox, simpledialog, filedialog
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import csv
from datetime import datetime

matplotlib.use("TkAgg")


class GradeTrendApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("成绩走势图")
        self.geometry("1000x600")
        self.minsize(800, 500)

        # 数据结构：list of dicts: {"label": str, "value": float, "when": datetime_or_str}
        self.data = []

        # 当前选择（可用于多学生/科目扩展）
        self.current_series_name = tk.StringVar(value="默认")

        self._build_ui()
        self._draw_plot()

    def _build_ui(self):
        # 顶部菜单 / 按钮
        top_frame = ttk.Frame(self)
        top_frame.pack(side="top", fill="x", padx=8, pady=6)

        ttk.Button(top_frame, text="添加成绩", command=self.add_point).pack(side="left")
        ttk.Button(top_frame, text="删除所选", command=self.delete_selected).pack(side="left", padx=(6, 0))
        ttk.Button(top_frame, text="编辑所选", command=self.edit_selected).pack(side="left", padx=(6, 0))
        ttk.Button(top_frame, text="导入 CSV", command=self.import_csv).pack(side="left", padx=(12, 0))
        ttk.Button(top_frame, text="导出 CSV", command=self.export_csv).pack(side="left", padx=(6, 0))
        ttk.Button(top_frame, text="清空所有", command=self.clear_all).pack(side="left", padx=(12, 0))

        ttk.Label(top_frame, text="系列名：").pack(side="left", padx=(12, 0))
        ttk.Entry(top_frame, textvariable=self.current_series_name, width=20).pack(side="left")

        # 主体：左侧数据列表与控制，右侧图表
        main = ttk.Frame(self)
        main.pack(fill="both", expand=True, padx=8, pady=6)

        left = ttk.Frame(main)
        left.pack(side="left", fill="y")

        # 数据列表（Treeview）
        ttk.Label(left, text="成绩列表").pack(anchor="w", padx=4, pady=(4, 0))
        columns = ("label", "value", "time")
        self.tree = ttk.Treeview(left, columns=columns, show="headings", selectmode="extended", height=20)
        self.tree.heading("label", text="标签/科目")
        self.tree.heading("value", text="成绩")
        self.tree.heading("time", text="时间")
        self.tree.column("label", width=140)
        self.tree.column("value", width=70, anchor="center")
        self.tree.column("time", width=160)
        self.tree.pack(fill="y", expand=False, padx=4, pady=4)
        self.tree.bind("<Double-1>", lambda e: self.edit_selected())

        # 数据上下移动
        btns = ttk.Frame(left)
        btns.pack(fill="x", padx=4, pady=(6, 0))
        ttk.Button(btns, text="上移", command=lambda: self.move_selected(-1)).pack(side="left", fill="x", expand=True)
        ttk.Button(btns, text="下移", command=lambda: self.move_selected(1)).pack(side="left", fill="x", expand=True, padx=(6,0))

        # 说明/帮助
        help_lbl = ttk.Label(left, text="提示：双击项编辑\n导入CSV格式：label,value,time(可选)\n时间格式: YYYY-MM-DD 或 YYYY-MM-DD HH:MM", justify="left")
        help_lbl.pack(anchor="w", padx=4, pady=(8,4))

        # 右侧：图形区域
        right = ttk.Frame(main)
        right.pack(side="left", fill="both", expand=True, padx=(8,0))

        # Matplotlib 图表
        self.fig, self.ax = plt.subplots(figsize=(6, 4))
        self.fig.tight_layout()
        self.canvas = FigureCanvasTkAgg(self.fig, master=right)
        self.canvas_widget = self.canvas.get_tk_widget()
        self.canvas_widget.pack(fill="both", expand=True)

        # 下方状态栏
        status = ttk.Frame(self)
        status.pack(side="bottom", fill="x")
        self.status_var = tk.StringVar(value="就绪")
        ttk.Label(status, textvariable=self.status_var).pack(side="left", padx=6, pady=4)

    def add_point(self):
        # 弹出对话：输入 label, value, time(optional)
        dialog = AddEditDialog(self, title="添加成绩")
        self.wait_window(dialog)
        if dialog.result is None:
            return
        label, value, when = dialog.result
        try:
            value_f = float(value)
        except ValueError:
            messagebox.showerror("错误", "成绩需为数字")
            return
        # 解析时间，如果为空则用当前时间
        when_parsed = self._parse_time(when)
        entry = {"label": label, "value": value_f, "time": when_parsed}
        self.data.append(entry)
        self._refresh_list()
        self._draw_plot()
        self.status_var.set(f"已添加：{label} {value_f}")

    def edit_selected(self):
        sel = self.tree.selection()
        if not sel:
            messagebox.showinfo("提示", "请选择要编辑的项")
            return
        # 只编辑第一个选中的
        iid = sel[0]
        idx = int(iid)
        current = self.data[idx]
        dialog = AddEditDialog(self, title="编辑成绩", initial=(current['label'], str(current['value']), self._time_to_str(current['time'])))
        self.wait_window(dialog)
        if dialog.result is None:
            return
        label, value, when = dialog.result
        try:
            value_f = float(value)
        except ValueError:
            messagebox.showerror("错误", "成绩需为数字")
            return
        when_parsed = self._parse_time(when)
        self.data[idx] = {"label": label, "value": value_f, "time": when_parsed}
        self._refresh_list()
        self._draw_plot()
        self.status_var.set(f"已编辑第 {idx+1} 项")

    def delete_selected(self):
        sel = self.tree.selection()
        if not sel:
            messagebox.showinfo("提示", "请选择要删除的项")
            return
        # 删除多个：按索引从大到小删除以保持索引有效
        idxs = sorted((int(i) for i in sel), reverse=True)
        for i in idxs:
            del self.data[i]
        self._refresh_list()
        self._draw_plot()
        self.status_var.set(f"已删除 {len(idxs)} 项")

    def move_selected(self, direction):
        sel = self.tree.selection()
        if not sel:
            return
        # 只处理第一个选中
        idx = int(sel[0])
        new_idx = idx + direction
        if not (0 <= new_idx < len(self.data)):
            return
        self.data[idx], self.data[new_idx] = self.data[new_idx], self.data[idx]
        self._refresh_list()
        # 重新选中移动后的项
        self.tree.selection_set(str(new_idx))
        self._draw_plot()

    def clear_all(self):
        if not self.data:
            return
        if not messagebox.askyesno("确认", "确定要清空所有数据吗？"):
            return
        self.data.clear()
        self._refresh_list()
        self._draw_plot()
        self.status_var.set("已清空所有数据")

    def import_csv(self):
        path = filedialog.askopenfilename(title="导入 CSV", filetypes=[("CSV 文件", "*.csv"), ("所有文件", "*.*")])
        if not path:
            return
        try:
            with open(path, newline='', encoding='utf-8') as f:
                reader = csv.reader(f)
                count = 0
                for row in reader:
                    if not row:
                        continue
                    # 支持 2 或 3 列：label,value[,time]
                    label = row[0].strip()
                    if len(row) < 2:
                        continue
                    value = row[1].strip()
                    when = row[2].strip() if len(row) >= 3 else ""
                    try:
                        value_f = float(value)
                    except ValueError:
                        continue
                    when_parsed = self._parse_time(when)
                    self.data.append({"label": label, "value": value_f, "time": when_parsed})
                    count += 1
            self._refresh_list()
            self._draw_plot()
            messagebox.showinfo("导入完成", f"已导入 {count} 条记录")
        except Exception as e:
            messagebox.showerror("错误", f"导入失败：{e}")

    def export_csv(self):
        if not self.data:
            messagebox.showinfo("提示", "没有数据可导出")
            return
        path = filedialog.asksaveasfilename(title="导出 CSV", defaultextension=".csv",
                                            filetypes=[("CSV 文件", "*.csv"), ("所有文件", "*.*")])
        if not path:
            return
        try:
            with open(path, 'w', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                for entry in self.data:
                    writer.writerow([entry['label'], entry['value'], self._time_to_str(entry['time'])])
            messagebox.showinfo("导出完成", "已导出 CSV")
        except Exception as e:
            messagebox.showerror("错误", f"导出失败：{e}")

    def _refresh_list(self):
        # 更新 Treeview 列表，iid 使用索引字符串
        self.tree.delete(*self.tree.get_children())
        for idx, entry in enumerate(self.data):
            label = entry['label']
            value = entry['value']
            tstr = self._time_to_str(entry['time'])
            self.tree.insert("", "end", iid=str(idx), values=(label, value, tstr))

    def _draw_plot(self):
        self.ax.clear()
        if not self.data:
            self.ax.set_title("暂无数据")
            self.canvas.draw()
            return
        # 用数据索引或时间作为横轴
        # 如果所有 time 都可解析为 datetime，则按时间排序并绘图，否则按数据顺序绘图
        times = []
        all_dt = True
        for e in self.data:
            if isinstance(e['time'], datetime):
                times.append(e['time'])
            else:
                # try parse string
                try:
                    dt = self._parse_time(e['time'])
                    if isinstance(dt, datetime):
                        times.append(dt)
                    else:
                        all_dt = False
                        break
                except Exception:
                    all_dt = False
                    break
        if all_dt and times:
            # 排序：保持原 data 顺序还是按时间排序？这里按当前 data 顺序但横轴使用时间值
            x = times
            # 为了可视化更好，格式化 x 轴
            self.ax.plot_date(x, [e['value'] for e in self.data], '-o', label=self.current_series_name.get())
            self.ax.xaxis.set_tick_params(rotation=30)
            self.ax.set_xlabel("时间")
        else:
            # 使用序号作为横轴
            x = list(range(1, len(self.data) + 1))
            self.ax.plot(x, [e['value'] for e in self.data], '-o', label=self.current_series_name.get())
            self.ax.set_xlabel("序号(可用上移/下移调整顺序)")

        self.ax.set_ylabel("成绩")
        self.ax.set_ylim(0, 100)  # 可根据需要调整或自动
        self.ax.set_title(f"成绩走势图 — {self.current_series_name.get()}")
        self.ax.grid(True, linestyle='--', alpha=0.6)
        self.ax.legend()
        self.fig.tight_layout()
        self.canvas.draw()

    def _parse_time(self, s):
        if not s:
            return datetime.now()
        if isinstance(s, datetime):
            return s
        s = s.strip()
        fmts = ("%Y-%m-%d %H:%M", "%Y-%m-%d", "%Y/%m/%d", "%Y.%m.%d")
        for f in fmts:
            try:
                return datetime.strptime(s, f)
            except Exception:
                pass
        # 解析失败则返回原字符串
        return s

    def _time_to_str(self, t):
        if isinstance(t, datetime):
            return t.strftime("%Y-%m-%d %H:%M")
        return str(t)


class AddEditDialog(tk.Toplevel):
    def __init__(self, master, title="添加/编辑", initial=None):
        super().__init__(master)
        self.title(title)
        self.transient(master)
        self.grab_set()

        self.result = None

        ttk.Label(self, text="标签/科目:").grid(row=0, column=0, sticky="e", padx=6, pady=6)
        self.label_var = tk.StringVar(value=(initial[0] if initial else ""))
        ttk.Entry(self, textvariable=self.label_var, width=30).grid(row=0, column=1, padx=6, pady=6)

        ttk.Label(self, text="成绩:").grid(row=1, column=0, sticky="e", padx=6, pady=6)
        self.value_var = tk.StringVar(value=(initial[1] if initial else ""))
        ttk.Entry(self, textvariable=self.value_var, width=20).grid(row=1, column=1, padx=6, pady=6)

        ttk.Label(self, text="时间(可选):").grid(row=2, column=0, sticky="e", padx=6, pady=6)
        self.time_var = tk.StringVar(value=(initial[2] if initial else ""))
        ttk.Entry(self, textvariable=self.time_var, width=30).grid(row=2, column=1, padx=6, pady=6)

        btn_frame = ttk.Frame(self)
        btn_frame.grid(row=3, column=0, columnspan=2, pady=(6,8))
        ttk.Button(btn_frame, text="确定", command=self._on_ok).pack(side="left", padx=6)
        ttk.Button(btn_frame, text="取消", command=self._on_cancel).pack(side="left", padx=6)

        self.bind("<Return>", lambda e: self._on_ok())
        self.bind("<Escape>", lambda e: self._on_cancel())

        # 居中
        self.update_idletasks()
        w = self.winfo_width()
        h = self.winfo_height()
        ws = self.master.winfo_width()
        hs = self.master.winfo_height()
        x = self.master.winfo_rootx() + (ws - w) // 2
        y = self.master.winfo_rooty() + (hs - h) // 2
        self.geometry(f"+{x}+{y}")

    def _on_ok(self):
        label = self.label_var.get().strip()
        value = self.value_var.get().strip()
        when = self.time_var.get().strip()
        if not label:
            messagebox.showerror("错误", "请填写标签/科目")
            return
        if not value:
            messagebox.showerror("错误", "请填写成绩")
            return
        self.result = (label, value, when)
        self.destroy()

    def _on_cancel(self):
        self.result = None
        self.destroy()


if __name__ == "__main__":
    app = GradeTrend