"""importers.py — 다기종 카트 export 파싱·정규화.

기능:
- reference.yaml 의 device_presets 로 흔한 컬럼명을 canonical(time/vo2/vco2/...)로 매핑.
- 단위 자동 인식: VO2 평균값이 작으면(< 25) L/min, 크면 mL/min 으로 추정.
- 사용자 정의 매핑 dict 지원.
- 정규화 결과: time_min, VO2_Lmin, VCO2_Lmin (+ 가능시 RQ_device, REE_device).

본 도구는 연구용·참고용이며 임상 의사결정에 직접 사용 금지.
"""

from __future__ import annotations

import os

import numpy as np
import pandas as pd
import yaml

_REF_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "reference.yaml")


def load_reference(path: str = _REF_PATH) -> dict:
    with open(path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def _find_col(df_cols, candidates):
    """후보 리스트 중 df 에 존재하는 첫 컬럼명 반환(대소문자/공백 무시 매칭)."""
    norm = {str(c).strip().lower(): c for c in df_cols}
    for cand in candidates:
        key = str(cand).strip().lower()
        if key in norm:
            return norm[key]
    return None


def infer_vo2_unit(series) -> str:
    """VO2 단위 추정. 분당 산소소비는 성인 안정시 ~0.2-0.4 L/min = 200-400 mL/min.
    중앙값 >= 25 면 mL/min, 아니면 L/min 으로 본다."""
    vals = pd.to_numeric(series, errors="coerce").dropna()
    if vals.empty:
        return "L/min"
    return "mL/min" if float(vals.median()) >= 25 else "L/min"


def build_mapping(df, device=None, user_mapping=None, reference=None):
    """canonical -> 실제 컬럼명 매핑 dict 생성.

    우선순위: user_mapping > device preset 자동탐색.
    user_mapping 예: {'time':'t','vo2':'VO2','vco2':'VCO2'}
    """
    reference = reference or load_reference()
    mapping = {"time": None, "vo2": None, "vco2": None,
               "rq_device": None, "ree_device": None}

    if device and device in reference.get("device_presets", {}):
        preset = reference["device_presets"][device]["columns"]
        for canon, cands in preset.items():
            mapping[canon] = _find_col(df.columns, cands)
    else:
        # 디바이스 미지정: 모든 프리셋 후보를 합쳐 탐색
        all_cands = {"time": [], "vo2": [], "vco2": [], "rq_device": [], "ree_device": []}
        for preset in reference.get("device_presets", {}).values():
            for canon, cands in preset["columns"].items():
                all_cands.setdefault(canon, []).extend(cands)
        for canon, cands in all_cands.items():
            mapping[canon] = _find_col(df.columns, cands)

    if user_mapping:
        for canon, col in user_mapping.items():
            if col:
                mapping[canon] = col
    return mapping


def normalize(df, device=None, user_mapping=None, vo2_unit="auto",
              reference=None):
    """raw DataFrame -> 정규화 DataFrame.

    반환 컬럼: time_min, VO2_Lmin, VCO2_Lmin, (있으면) RQ_device, REE_device
    vo2_unit: 'auto'|'L/min'|'mL/min' (VO2·VCO2 공통 적용).
    """
    reference = reference or load_reference()
    mapping = build_mapping(df, device, user_mapping, reference)

    missing = [k for k in ("vo2", "vco2") if mapping.get(k) is None]
    if missing:
        raise ValueError(f"필수 컬럼 매핑 실패: {missing}. 컬럼들={list(df.columns)}")

    vo2_raw = pd.to_numeric(df[mapping["vo2"]], errors="coerce")
    vco2_raw = pd.to_numeric(df[mapping["vco2"]], errors="coerce")

    unit = vo2_unit
    if unit == "auto":
        unit = infer_vo2_unit(vo2_raw)
    factor = 1.0 / 1000.0 if unit == "mL/min" else 1.0

    out = pd.DataFrame()
    # 시간: 매핑되면 사용, 아니면 인덱스를 분으로 가정(1분 간격)
    if mapping.get("time") is not None:
        tcol = pd.to_numeric(df[mapping["time"]], errors="coerce")
        out["time_min"] = tcol.values
    else:
        out["time_min"] = np.arange(len(df), dtype=float)

    out["VO2_Lmin"] = (vo2_raw * factor).values
    out["VCO2_Lmin"] = (vco2_raw * factor).values

    if mapping.get("rq_device") is not None:
        out["RQ_device"] = pd.to_numeric(df[mapping["rq_device"]], errors="coerce").values
    if mapping.get("ree_device") is not None:
        out["REE_device"] = pd.to_numeric(df[mapping["ree_device"]], errors="coerce").values

    out.attrs["vo2_unit_detected"] = unit
    out.attrs["mapping"] = mapping
    return out


def load_csv_normalized(path, device=None, user_mapping=None, vo2_unit="auto"):
    """CSV 경로 -> 정규화 DataFrame (편의 함수)."""
    df = pd.read_csv(path)
    return normalize(df, device=device, user_mapping=user_mapping, vo2_unit=vo2_unit)
