"""
MASHBiospecimenCoC-Kor (매시바이오스페시먼체인코어)
====================================================
MASH 임상시험 검체 chain-of-custody & central lab 물류 추적기 (standalone Streamlit).

도메인: MASLD | 카테고리: 인체실험 도구 (임상시험 운영 물류)

실행:  pip install -r requirements.txt && streamlit run app.py

* 외부 네트워크/API 호출 없음. 모든 데이터는 합성(데모) 또는 사용자 업로드.
* 본 도구는 연구·운영 보조용 참고 도구이며 실제 임상시험 규제 의사결정을 대체하지 않는다.
"""

import io

import pandas as pd
import streamlit as st

try:
    import plotly.express as px
    HAS_PLOTLY = True
except Exception:  # plotly 미설치 시에도 앱은 동작(차트만 대체)
    HAS_PLOTLY = False

import logic

st.set_page_config(
    page_title="MASHBiospecimenCoC-Kor",
    page_icon="🧪",
    layout="wide",
)

DISCLAIMER = (
    "⚠️ **디스클레이머**: 본 도구는 연구·운영 보조용 참고 도구이며 "
    "실제 임상시험 규제 의사결정을 대체하지 않습니다. "
    "표시되는 데이터는 합성(데모) 또는 사용자가 업로드한 데이터입니다."
)


# ---------------------------------------------------------------------------
# 데이터 로딩 (데모 기본 + 선택적 CSV 업로드 override)
# ---------------------------------------------------------------------------
@st.cache_data
def load_demo():
    return logic.load_all()


def get_data():
    demo = load_demo()
    data = {k: v.copy() for k, v in demo.items()}

    with st.sidebar:
        st.header("데이터 소스")
        st.caption(
            "기본은 합성 데모 데이터. 아래에서 CSV를 업로드하면 해당 데이터셋만 교체됩니다. "
            "(업로드 없이도 즉시 시연 가능)"
        )
        up_manifest = st.file_uploader("specimen_manifest.csv", type="csv", key="m")
        up_custody = st.file_uploader("custody_log.csv", type="csv", key="c")
        up_shipment = st.file_uploader("shipment_log.csv", type="csv", key="s")
        up_path = st.file_uploader("pathology_turnaround.csv", type="csv", key="p")

        if up_manifest is not None:
            data["manifest"] = pd.read_csv(up_manifest)
            st.success("manifest 교체됨")
        if up_custody is not None:
            data["custody"] = pd.read_csv(up_custody)
            st.success("custody 교체됨")
        if up_shipment is not None:
            data["shipment"] = pd.read_csv(up_shipment)
            st.success("shipment 교체됨")
        if up_path is not None:
            data["pathology"] = pd.read_csv(up_path)
            st.success("pathology 교체됨")

        st.divider()
        st.caption("참조 표준: IATA PI650 (검체 운송), ICH GCP (검체 사슬), MASH 시험 typical visit schedule")
    return data


def _download_button(df: pd.DataFrame, label: str, fname: str):
    buf = io.StringIO()
    df.to_csv(buf, index=False)
    st.download_button(label, buf.getvalue(), file_name=fname, mime="text/csv")


# ---------------------------------------------------------------------------
# 메인
# ---------------------------------------------------------------------------
def main():
    st.title("🧪 MASHBiospecimenCoC-Kor")
    st.caption("MASH 시험 검체 chain-of-custody & central lab 물류 추적기 · 도메인: MASLD")
    st.warning(DISCLAIMER)

    if not HAS_PLOTLY:
        st.info("plotly 미설치 — 차트는 Streamlit 기본 차트로 대체됩니다. "
                "`pip install -r requirements.txt` 로 전체 기능을 사용하세요.")

    data = get_data()
    manifest = data["manifest"]
    custody = data["custody"]
    shipment = data["shipment"]
    pathology = data["pathology"]
    visit_schedule = data["visit_schedule"]

    tabs = st.tabs([
        "1️⃣ Schedule Window 준수",
        "2️⃣ Chain-of-Custody 원장",
        "3️⃣ Cold-chain Shipment QC",
        "4️⃣ Pathology Turnaround",
        "5️⃣ Block 재고·재채취 대장",
    ])

    # ---------------- 기능 1 ----------------
    with tabs[0]:
        st.subheader("검체 schedule window 준수 추적")
        st.caption("환자별 visit별 biopsy·ELF·PRO-C3·VCTE 채취 due/완료/window 위반 alert")
        summ = logic.schedule_compliance_summary(manifest)
        c1, c2, c3, c4 = st.columns(4)
        c1.metric("due 도래 검체", summ["due_rows"])
        c2.metric("채취율", f"{summ['collection_rate']*100:.1f}%")
        c3.metric("미채취(missed)", summ["missed"])
        c4.metric("window 위반", summ["window_violations"])

        with st.expander("MASH 시험 typical visit schedule (참조)"):
            st.dataframe(visit_schedule, use_container_width=True, hide_index=True)

        st.markdown("**🚨 Alert 대상 (missed 또는 window 위반)**")
        alerts = logic.window_alert_list(manifest)
        st.dataframe(alerts, use_container_width=True, hide_index=True)
        _download_button(alerts, "alert 목록 CSV 다운로드", "window_alerts.csv")

        st.markdown("**검체별 상태 분포**")
        status_ct = manifest.groupby(["specimen_type", "status"]).size().reset_index(name="count")
        if HAS_PLOTLY:
            fig = px.bar(status_ct, x="specimen_type", y="count", color="status",
                         title="검체 종류별 채취 상태")
            st.plotly_chart(fig, use_container_width=True)
        else:
            st.bar_chart(status_ct.pivot(index="specimen_type", columns="status",
                                         values="count").fillna(0))

    # ---------------- 기능 2 ----------------
    with tabs[1]:
        st.subheader("Chain-of-custody 원장")
        st.caption("채취→site 보관→shipment→central lab 인수 각 단계 timestamp·담당자·온도 (GCP/GLP 사슬)")
        comp = logic.custody_completeness(custody)
        c1, c2, c3 = st.columns(3)
        c1.metric("추적 검체 수", comp["specimen_uid"].nunique())
        c2.metric("사슬 완전(complete)", int((comp["chain_complete"] == "yes").sum()))
        c3.metric("사슬 불완전(incomplete)", int((comp["chain_complete"] == "no").sum()))

        st.markdown("**검체 선택 → 단계별 custody 타임라인**")
        uids = sorted(custody["specimen_uid"].unique())
        sel = st.selectbox("specimen_uid", uids)
        chain = custody[custody["specimen_uid"] == sel].sort_values("timestamp")
        st.dataframe(chain, use_container_width=True, hide_index=True)

        st.markdown("**사슬 완전성 요약**")
        st.dataframe(comp, use_container_width=True, hide_index=True)
        incomplete = comp[comp["chain_complete"] == "no"]
        if len(incomplete):
            st.warning(f"사슬 불완전 검체 {len(incomplete)}건 — 누락 단계 확인 필요")
        _download_button(comp, "custody 완전성 CSV", "custody_completeness.csv")

    # ---------------- 기능 3 ----------------
    with tabs[2]:
        st.subheader("Cold-chain shipment QC")
        st.caption("IATA PI650 기반 운송 조건·온도일탈·용혈/integrity reject 분류·재채취 trigger")
        qc = logic.shipment_qc_summary(shipment)
        c1, c2, c3, c4 = st.columns(4)
        c1.metric("총 shipment", qc["total_shipments"])
        c2.metric("인수 승인율", f"{qc['accept_rate']*100:.1f}%")
        c3.metric("온도 일탈", qc["temp_excursions"])
        c4.metric("재채취 trigger", qc["recollections_triggered"])

        st.markdown("**Reject 사유 분류**")
        if qc["reject_reasons"]:
            rej_df = pd.DataFrame(
                [{"reject_reason": k, "count": v} for k, v in qc["reject_reasons"].items()]
            )
            if HAS_PLOTLY:
                fig = px.pie(rej_df, names="reject_reason", values="count",
                             title="Shipment reject 사유")
                st.plotly_chart(fig, use_container_width=True)
            else:
                st.dataframe(rej_df, hide_index=True)
        else:
            st.info("reject 사유 없음")

        st.markdown("**운송 시간 분포 (transit hours)**")
        if HAS_PLOTLY:
            fig = px.histogram(shipment, x="transit_hours", nbins=20,
                               title=f"중앙값 {qc['median_transit_hours']:.0f}h")
            st.plotly_chart(fig, use_container_width=True)
        else:
            st.bar_chart(shipment["transit_hours"])

        st.markdown("**🚨 재채취 trigger 목록**")
        rec = logic.recollection_trigger_list(shipment)
        st.dataframe(rec, use_container_width=True, hide_index=True)
        _download_button(rec, "재채취 trigger CSV", "recollection_triggers.csv")

    # ---------------- 기능 4 ----------------
    with tabs[3]:
        st.subheader("Central pathology turnaround")
        st.caption("block 접수→판독 완료 turnaround·적체(backlog)·재판독 추적")
        ts = logic.turnaround_summary(pathology)
        c1, c2, c3, c4 = st.columns(4)
        c1.metric("총 block", ts["total_blocks"])
        c2.metric("판독 완료", ts["read_complete"])
        c3.metric("적체(pending)", ts["pending_backlog"])
        c4.metric("재판독 필요", ts["reread_required"])
        c5, c6 = st.columns(2)
        c5.metric("중앙 turnaround(일)", f"{ts['median_turnaround_days']:.0f}")
        c6.metric(f"TAT 초과(>{ts['target_tat_days']}일)", ts["tat_breaches"])

        st.markdown("**Turnaround 분포**")
        done = pathology[pathology["read_status"] == "read_complete"].copy()
        done["turnaround_days"] = pd.to_numeric(done["turnaround_days"], errors="coerce")
        if HAS_PLOTLY and len(done):
            fig = px.histogram(done, x="turnaround_days", nbins=20,
                               title=f"목표 TAT {ts['target_tat_days']}일")
            fig.add_vline(x=ts["target_tat_days"], line_dash="dash", line_color="red")
            st.plotly_chart(fig, use_container_width=True)
        elif len(done):
            st.bar_chart(done["turnaround_days"])

        st.markdown("**🚨 backlog / TAT 초과 / 재판독 목록**")
        bl = logic.pathology_backlog_list(pathology)
        st.dataframe(bl, use_container_width=True, hide_index=True)
        _download_button(bl, "pathology backlog CSV", "pathology_backlog.csv")

    # ---------------- 기능 5 ----------------
    with tabs[4]:
        st.subheader("Block 재고·재채취 대장")
        st.caption("잔여 block·재채취 필요 환자·evaluable 손실 위험 환자 list")

        st.markdown("**Block 재고 (잔여 슬라이드 residual_sections)**")
        inv = logic.block_inventory(pathology)
        low = inv[inv["residual_sections"] <= 1]
        st.metric("잔여 슬라이드 ≤1 (재절편 위험)", len(low))
        st.dataframe(inv, use_container_width=True, hide_index=True)

        st.markdown("**재채취 필요 대장 (missed + shipment QC reject)**")
        reg = logic.recollection_register(manifest, shipment)
        st.dataframe(reg, use_container_width=True, hide_index=True)
        _download_button(reg, "재채취 대장 CSV", "recollection_register.csv")

        st.markdown("**🚨 Evaluable 손실 위험 환자 (1차 조직학 종결점 관점)**")
        risk = logic.evaluable_loss_risk(manifest, shipment, pathology)
        if len(risk):
            st.dataframe(risk, use_container_width=True, hide_index=True)
            if HAS_PLOTLY:
                top = risk.head(20)
                fig = px.bar(top, x="patient_id", y="n_risk_factors", color="site_id",
                             title="위험 요인 수 상위 환자")
                st.plotly_chart(fig, use_container_width=True)
            _download_button(risk, "evaluable 손실 위험 CSV", "evaluable_loss_risk.csv")
        else:
            st.success("evaluable 손실 위험 환자 없음")

    st.divider()
    st.caption(DISCLAIMER)


if __name__ == "__main__":
    main()
