# © 四川森普力科技有限公司  联系人：姚工  电话：159-2808-9752/177-8067-0842
# 邮件：mylanview@126.com   网址：www.sploptics.com
"""E3 — 红宝石荧光压力测量插件（调用 红宝石拟合_最优解.py）"""

from dataclasses import dataclass
from pathlib import Path
from typing import Optional
import importlib.util
import sys
import numpy as np

try:
    from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout,
                                  QLabel, QComboBox, QLineEdit,
                                  QPushButton, QDoubleSpinBox, QFileDialog,
                                  QMessageBox)
    _HAS_QT = True
except ImportError:
    _HAS_QT = False

_HERE = Path(__file__).parent


def _load_ruby_module():
    """动态加载 红宝石拟合_最优解.py（禁止修改该文件）。"""
    path = _HERE / "红宝石拟合_最优解.py"
    if not path.exists():
        raise ImportError("找不到红宝石拟合_最优解.py")
    spec = importlib.util.spec_from_file_location("_ruby_optimal", str(path))
    mod = importlib.util.module_from_spec(spec)
    try:
        spec.loader.exec_module(mod)
    except ImportError as e:
        raise ImportError(f"缺少依赖，请执行: pip install scipy pandas\n原始错误: {e}")
    return mod


@dataclass
class RubyFitResult:
    R1_nm: float
    R1_err: float
    R2_nm: Optional[float]
    fwhm_R1: float
    fwhm_R2: Optional[float]
    P_GPa: float
    P_err: float
    pressure_scale: str
    model: str
    r_squared: float
    aic: float
    snr: float
    fit_wl: np.ndarray
    fit_int: np.ndarray
    raw_wl: np.ndarray
    raw_int: np.ndarray
    roi_nm: tuple


def _do_fit(
    wl: np.ndarray,
    intensities: np.ndarray,
    pressure_scale: str,
    roi: tuple,
) -> RubyFitResult:
    """适配层，调用 红宝石拟合_最优解.py 的 fit_ruby 函数。"""
    try:
        mod = _load_ruby_module()
    except ImportError as e:
        from plugins.base_plugin import PluginLoadError  # type: ignore
        raise PluginLoadError(str(e))

    # 截取 ROI
    mask = (wl >= roi[0]) & (wl <= roi[1])
    wl_roi = wl[mask]
    int_roi = intensities[mask]

    if len(wl_roi) < 20:
        raise ValueError("ROI 内数据点不足（需 ≥ 20 点）")

    # 调用原文件的 fit_ruby 函数（返回结果字典或直接打印）
    # fit_ruby 签名: fit_ruby(wavelength, counts, model='auto', baseline_order=1,
    #                        scale='mao_nonhydrostatic', roi=None, ...)
    try:
        result = mod.fit_ruby(
            wl_roi, int_roi,
            model="auto",
            baseline_order=1,
            scale=pressure_scale,
            roi=None,           # 已手动截取
            verbose=False,
        )
    except TypeError:
        # 旧接口兼容
        result = mod.fit_ruby(wl_roi, int_roi)

    # 从返回值提取（以实际接口为准，此处做通用适配）
    if isinstance(result, dict):
        r1 = float(result.get("R1_nm", result.get("r1_fit", 694.22)))
        r1_err = float(result.get("R1_err", result.get("r1_err", 0.005)))
        r2 = result.get("R2_nm", result.get("r2_fit", None))
        fwhm1 = float(result.get("fwhm_R1", result.get("fwhm1", 0.5)))
        fwhm2 = result.get("fwhm_R2", result.get("fwhm2", None))
        p_gpa = float(result.get("P_GPa", result.get("P", 0.0)))
        p_err = float(result.get("P_err", 0.02))
        model = str(result.get("model", "auto"))
        r2_val = float(result.get("r_squared", result.get("r2", 0.99)))
        aic = float(result.get("aic", 0.0))
        snr = float(result.get("snr", 10.0))
        fit_wl = np.asarray(result.get("fit_wl", wl_roi))
        fit_int = np.asarray(result.get("fit_int", int_roi))
    else:
        # 无法解析时退化：直接用压力公式
        r1 = float(wl_roi[np.argmax(int_roi)])
        r1_err = 0.005
        r2 = None
        fwhm1 = 0.5
        fwhm2 = None
        p_gpa = float(mod.ruby_pressure(r1, scale=pressure_scale))
        p_err = abs(float(mod.ruby_pressure_gradient(r1, scale=pressure_scale)) * r1_err)
        model = "argmax"
        r2_val = 0.0
        aic = 0.0
        snr = 5.0
        fit_wl = wl_roi
        fit_int = int_roi

    return RubyFitResult(
        R1_nm=r1, R1_err=r1_err,
        R2_nm=float(r2) if r2 is not None else None,
        fwhm_R1=fwhm1,
        fwhm_R2=float(fwhm2) if fwhm2 is not None else None,
        P_GPa=p_gpa, P_err=p_err,
        pressure_scale=pressure_scale,
        model=model,
        r_squared=r2_val, aic=aic, snr=snr,
        fit_wl=fit_wl, fit_int=fit_int,
        raw_wl=wl_roi, raw_int=int_roi,
        roi_nm=roi,
    )


# ── 插件主类 ─────────────────────────────────────────────────────────────

try:
    from plugins.base_plugin import PluginInterface, PluginInfo, PluginInput, PluginOutput  # type: ignore
except ImportError:
    # 直接从相对路径导入（插件目录启动时）
    import sys, os
    sys.path.insert(0, str(_HERE.parent.parent))
    from plugins.base_plugin import PluginInterface, PluginInfo, PluginInput, PluginOutput


class RubyPressurePlugin(PluginInterface):

    DEFAULT_ROI = (690.0, 698.0)
    DEFAULT_SCALE = "mao_nonhydrostatic"

    def __init__(self):
        self._roi = self.DEFAULT_ROI
        self._scale = self.DEFAULT_SCALE
        self._last_result: Optional[RubyFitResult] = None
        self._pending_input = None
        self._widget = None

    def get_info(self) -> PluginInfo:
        return PluginInfo(
            plugin_id="ruby_pressure_v1",
            name="红宝石荧光压力测量",
            version="1.0.0",
            author="四川森普力科技有限公司",
            description="拟合红宝石R1/R2荧光峰，通过Mao公式计算压力（GPa）",
            input_type="spectrum",
            output_type="json",
            requires_stop=False,
        )

    def validate_input(self, plugin_input: PluginInput) -> tuple:
        wl = plugin_input.wl
        roi_lo, roi_hi = self._roi
        if float(wl[0]) > roi_lo or float(wl[-1]) < roi_hi:
            return False, f"波长范围需覆盖 {roi_lo}–{roi_hi}nm（当前 ROI）"
        mask = (wl >= roi_lo) & (wl <= roi_hi)
        if mask.sum() < 20:
            return False, f"ROI {roi_lo}–{roi_hi}nm 内数据点不足（需 ≥ 20）"
        return True, ""

    def run(self, plugin_input: PluginInput) -> PluginOutput:
        fit = _do_fit(plugin_input.wl, plugin_input.intensities,
                      self._scale, self._roi)
        self._last_result = fit

        r2_str = f"  R2={fit.R2_nm:.3f}nm" if fit.R2_nm else ""
        summary = (f"R1={fit.R1_nm:.3f}nm{r2_str}  "
                   f"P={fit.P_GPa:.3f}±{fit.P_err:.3f} GPa  "
                   f"R²={fit.r_squared:.4f}")

        result = {
            "R1_nm": fit.R1_nm, "R1_err": fit.R1_err,
            "R2_nm": fit.R2_nm,
            "fwhm_R1": fit.fwhm_R1, "fwhm_R2": fit.fwhm_R2,
            "P_GPa": fit.P_GPa, "P_err": fit.P_err,
            "pressure_scale": fit.pressure_scale,
            "model": fit.model,
            "r_squared": fit.r_squared, "aic": fit.aic, "snr": fit.snr,
            "roi_nm": list(fit.roi_nm),
        }
        return PluginOutput(plugin_id="ruby_pressure_v1", result=result, summary=summary)

    def get_ui_widget(self):
        if not _HAS_QT:
            return None
        if self._widget is not None:
            return self._widget

        self._widget = QWidget()
        layout = QVBoxLayout(self._widget)

        # 压力标度选择
        row1 = QHBoxLayout()
        row1.addWidget(QLabel("压力标度："))
        self._cb_scale = QComboBox()
        self._cb_scale.addItems([
            "mao_nonhydrostatic", "mao_hydrostatic",
            "dewaele", "dorogokupets",
        ])
        self._cb_scale.currentTextChanged.connect(lambda s: setattr(self, "_scale", s))
        row1.addWidget(self._cb_scale)
        row1.addStretch()
        layout.addLayout(row1)

        # ROI 输入
        row2 = QHBoxLayout()
        row2.addWidget(QLabel("ROI（nm）："))
        self._spin_lo = QDoubleSpinBox()
        self._spin_lo.setRange(600, 750)
        self._spin_lo.setValue(690.0)
        self._spin_lo.setDecimals(1)
        self._spin_hi = QDoubleSpinBox()
        self._spin_hi.setRange(600, 750)
        self._spin_hi.setValue(698.0)
        self._spin_hi.setDecimals(1)
        self._spin_lo.valueChanged.connect(lambda v: setattr(self, "_roi", (v, self._roi[1])))
        self._spin_hi.valueChanged.connect(lambda v: setattr(self, "_roi", (self._roi[0], v)))
        row2.addWidget(self._spin_lo)
        row2.addWidget(QLabel("–"))
        row2.addWidget(self._spin_hi)
        row2.addStretch()
        layout.addLayout(row2)

        # 结果显示
        self._lbl_result = QLabel("（尚未计算）")
        layout.addWidget(self._lbl_result)

        # 按钮行：立即计算 + 保存结果CSV
        row3 = QHBoxLayout()
        btn_calc = QPushButton("立即计算")
        btn_calc.setMaximumWidth(100)
        btn_calc.setToolTip("使用当前谱线立即运行红宝石压力计算")
        btn_calc.clicked.connect(self._on_calc_clicked)
        btn_save = QPushButton("保存结果CSV")
        btn_save.setMaximumWidth(120)
        btn_save.setToolTip("将最近一次计算结果导出为CSV文件")
        btn_save.clicked.connect(self._on_save_csv_clicked)
        row3.addWidget(btn_calc)
        row3.addWidget(btn_save)
        row3.addStretch()
        layout.addLayout(row3)

        layout.addStretch()
        return self._widget

    def _on_calc_clicked(self):
        """立即计算按钮：要求外部通过 set_current_input 传入最新谱线。"""
        if self._pending_input is None:
            self._lbl_result.setText("暂无谱线数据，请先采集快照或导入CSV。")
            return
        try:
            output = self.run(self._pending_input)
            self._lbl_result.setText(output.summary)
        except Exception as e:
            self._lbl_result.setText(f"计算失败：{e}")

    def set_current_input(self, plugin_input):
        """由 M0 在快照/导入后调用，缓存最新谱线供「立即计算」使用。"""
        self._pending_input = plugin_input

    def _on_save_csv_clicked(self):
        """保存结果CSV按钮。"""
        if self._last_result is None:
            QMessageBox.warning(self._widget, "提示", "尚无计算结果，请先运行计算。")
            return
        path, _ = QFileDialog.getSaveFileName(
            self._widget, "保存红宝石压力报告", "ruby_pressure_result.csv",
            "CSV 文件 (*.csv)"
        )
        if not path:
            return
        try:
            self._export_csv(path, self._last_result)
            QMessageBox.information(self._widget, "保存成功", f"已保存至：{path}")
        except Exception as e:
            QMessageBox.critical(self._widget, "保存失败", str(e))

    def _export_csv(self, filepath: str, fit: "RubyFitResult"):
        """按 CLAUDE.md 规定格式导出 CSV。"""
        import datetime
        from pathlib import Path

        # 压力标度参数
        _SCALES = {
            "mao_nonhydrostatic": (1904.0, 5.0,    694.22),
            "mao_hydrostatic":    (1904.0, 7.665,  694.22),
            "dewaele":            (1920.0, 9.61,   694.24),
            "dorogokupets":       (1876.0, 10.71,  694.24),
        }
        A, B, R10 = _SCALES.get(fit.pressure_scale, (1904.0, 5.0, 694.22))
        ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        r2_val = f"{fit.R2_nm:.3f}" if fit.R2_nm is not None else ""

        lines = [
            "# 红宝石压力测量报告",
            f"# 测量时间: {ts}",
            f"# 压力标度: {fit.pressure_scale} (A={A}, B={B}, R1_0={R10}nm)",
            "R1峰位(nm), R1误差(nm), R2峰位(nm), FWHM_R1(nm), 压力(GPa), 压力误差(GPa)",
            f"{fit.R1_nm:.3f}, {fit.R1_err:.3f}, {r2_val}, {fit.fwhm_R1:.3f}, "
            f"{fit.P_GPa:.3f}, {fit.P_err:.3f}",
        ]
        Path(filepath).write_text("\n".join(lines), encoding="utf-8")
