#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MASHFibroTargetGap-Kor (매시파이브로타겟갭코어)
=================================================
MASH fibrosis drug-target landscape gap mining + Korean cohort PoC suitability
+ 한국어 grant/IIT proposal card generator.

본 도구는 연구용·참고용 drug-target gap hypothesis 생성기입니다.
실제 약물 개발 결정·임상의사결정·승인 결정에 사용 금지.
"""
import argparse
import csv
import json
import os
import sys
from collections import defaultdict

ROOT = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(ROOT, "data")

DISCLAIMER = (
    "[디스클레이머] 본 도구는 연구용·참고용 drug-target gap hypothesis 생성기입니다. "
    "실제 약물 개발 결정·임상의사결정·승인 결정에 사용 금지."
)

# ---------------------------------------------------------------------------
# Data loaders
# ---------------------------------------------------------------------------
def _load_csv(name):
    path = os.path.join(DATA_DIR, name)
    if not os.path.isfile(path):
        raise FileNotFoundError("CSV not found: %s" % path)
    with open(path, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        return list(reader)


def load_all():
    drugs = _load_csv("mash_drugs.csv")
    mechanisms = _load_csv("mechanism_ontology.csv")
    coverage = _load_csv("coverage_matrix.csv")
    gaps = _load_csv("gap_targets.csv")
    korean = _load_csv("korean_poc.csv")
    return drugs, mechanisms, coverage, gaps, korean


# ---------------------------------------------------------------------------
# Rendering helpers
# ---------------------------------------------------------------------------
def _pad(s, w):
    s = "" if s is None else str(s)
    # crude width handling: Korean chars count as 2
    visible = 0
    for ch in s:
        visible += 2 if ord(ch) > 0x2E80 else 1
    pad = max(0, w - visible)
    return s + (" " * pad)


def hline(widths, ch="-"):
    return "+" + "+".join(ch * (w + 2) for w in widths) + "+"


def table(headers, rows, widths=None):
    if widths is None:
        widths = []
        for i, h in enumerate(headers):
            mx = len(h)
            for r in rows:
                mx = max(mx, len(str(r[i])) if i < len(r) and r[i] is not None else 0)
            widths.append(min(mx, 28))
    out = [hline(widths)]
    out.append("| " + " | ".join(_pad(h, w) for h, w in zip(headers, widths)) + " |")
    out.append(hline(widths, "="))
    for r in rows:
        out.append("| " + " | ".join(_pad(c, w) for c, w in zip(r, widths)) + " |")
    out.append(hline(widths))
    return "\n".join(out)


# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------
def cmd_summary():
    drugs, mechanisms, coverage, gaps, korean = load_all()
    print(DISCLAIMER)
    print()
    print("=" * 72)
    print("MASHFibroTargetGap-Kor (매시파이브로타겟갭코어) 요약")
    print("=" * 72)
    print("도메인        : MASLD / MASH fibrosis")
    print("약물 pipeline : %d종" % len(drugs))
    print("Mechanism     : %d종" % len(mechanisms))
    print("Coverage cells: %d개" % len(coverage))
    print("Gap targets   : %d개" % len(gaps))
    print("Korean PoC    : %d개 candidate" % len(korean))
    print()
    print("핵심 기능 (5):")
    print("  1) 약물 pipeline ingest + mechanism mapping")
    print("  2) 분자기전 25+ ontology + drug × mechanism coverage matrix")
    print("  3) 미충족 gap ranking + 차세대 target 후보")
    print("  4) Korean MASH cohort PoC suitability")
    print("  5) 한국어 grant/IIT proposal card + OpenClaw export")
    print()
    print("주요 명령: --coverage-matrix | --gap-rank --top N | --pipeline")
    print("          --target-candidates 'mech_name' | --korean-poc 'target'")
    print("          --grant-card --target 'name' | --export-openclaw out.json")


def cmd_pipeline():
    drugs, *_ = load_all()
    print(DISCLAIMER)
    print()
    print("MASH/NASH 약물 pipeline (%d종, 합성 데이터)" % len(drugs))
    print()
    headers = ["drug_id", "drug_name", "sponsor", "target_class", "phase", "N", "endpoint", "biomarker", "status"]
    widths = [7, 18, 14, 22, 18, 6, 22, 12, 12]
    rows = []
    for d in drugs:
        rows.append([
            d["drug_id"], d["drug_name"], d["sponsor"], d["target_class"],
            d["phase"], d["n_enrolled"], d["primary_endpoint"][:22],
            d["biomarker_type"], d["status"]
        ])
    print(table(headers, rows, widths))


def cmd_coverage_matrix():
    drugs, mechanisms, coverage, *_ = load_all()
    print(DISCLAIMER)
    print()
    print("Drug × Mechanism coverage matrix (cell = max coverage_score)")
    print("범례: . = 0  | p = preclinical(0.3)  | 1 = phase1(0.5)")
    print("      2 = phase2(0.7)  | 3 = phase3(1.0)  | A = approved(1.2)")
    print()

    cmap = defaultdict(float)
    for row in coverage:
        key = (row["drug_id"], row["mechanism_id"])
        try:
            s = float(row["coverage_score"])
        except ValueError:
            s = 0.0
        if s > cmap[key]:
            cmap[key] = s

    def render(score):
        if score >= 1.2:
            return "A"
        if score >= 1.0:
            return "3"
        if score >= 0.7:
            return "2"
        if score >= 0.5:
            return "1"
        if score >= 0.3:
            return "p"
        return "."

    # Mechanism columns header
    mech_ids = [m["mechanism_id"] for m in mechanisms]
    mech_names = {m["mechanism_id"]: m["mechanism_name"] for m in mechanisms}

    # Print legend mapping mech_id -> name
    print("Mechanism columns:")
    for i in range(0, len(mech_ids), 5):
        chunk = mech_ids[i:i + 5]
        parts = ["%s=%s" % (mid, mech_names[mid][:14]) for mid in chunk]
        print("  " + " | ".join(parts))
    print()

    # Header line
    head = "drug_id  drug_name           " + " ".join(m[-2:] for m in mech_ids)
    print(head)
    print("-" * len(head))
    totals = defaultdict(float)
    for d in drugs:
        row_cells = []
        for m in mech_ids:
            score = cmap.get((d["drug_id"], m), 0.0)
            row_cells.append(render(score))
            totals[m] += score
        name = d["drug_name"][:18]
        line = "%-8s %-20s" % (d["drug_id"], name) + " ".join("%2s" % c for c in row_cells)
        print(line)
    print()
    # Column totals
    tot_row = "TOTAL                        " + " ".join("%2.1f" % totals[m] for m in mech_ids)
    print(tot_row)
    print()
    print("Coverage matrix: %d drugs × %d mechanisms" % (len(drugs), len(mech_ids)))


def _gap_rank(gaps, mechanisms):
    name_by_id = {m["mechanism_id"]: m["mechanism_name"] for m in mechanisms}
    cat_by_id = {m["mechanism_id"]: m["mechanism_category"] for m in mechanisms}
    rows = []
    for g in gaps:
        try:
            gs = float(g["gap_score"])
        except ValueError:
            gs = 0.0
        try:
            n_drugs = int(g["current_attack_drug_count"])
        except ValueError:
            n_drugs = 0
        try:
            p3 = int(g["phase_3_count"])
        except ValueError:
            p3 = 0
        rows.append({
            "mechanism_id": g["mechanism_id"],
            "mechanism_name": name_by_id.get(g["mechanism_id"], g["mechanism_id"]),
            "category": cat_by_id.get(g["mechanism_id"], "?"),
            "gap_score": gs,
            "n_drugs": n_drugs,
            "phase_3": p3,
            "next_gen": g["next_gen_target_candidate_list"],
            "preclin": g["preclinical_pubmed_n"],
            "ko_pheno": g["ko_mouse_phenotype"],
            "gwas": g["human_gwas_association"],
        })
    rows.sort(key=lambda r: (-r["gap_score"], r["n_drugs"]))
    return rows


def cmd_gap_rank(top):
    drugs, mechanisms, coverage, gaps, korean = load_all()
    ranked = _gap_rank(gaps, mechanisms)
    print(DISCLAIMER)
    print()
    print("Top %d 미충족 mechanism gap ranking" % top)
    print()
    headers = ["rank", "mech_id", "mechanism", "category", "gap_score", "drugs", "phase3", "preclin_n"]
    widths = [5, 7, 22, 14, 10, 6, 7, 10]
    rows = []
    for i, r in enumerate(ranked[:top], 1):
        rows.append([
            str(i), r["mechanism_id"], r["mechanism_name"], r["category"],
            "%.1f" % r["gap_score"], str(r["n_drugs"]), str(r["phase_3"]), r["preclin"]
        ])
    print(table(headers, rows, widths))
    print()
    print("해석: gap_score↑ = attack 약물 적고/phase3 도달 없음/미충족 큼.")


def _find_mechanism(query, mechanisms):
    q = query.strip().lower()
    for m in mechanisms:
        if m["mechanism_id"].lower() == q:
            return m
        if m["mechanism_name"].lower() == q:
            return m
    # substring search
    for m in mechanisms:
        if q in m["mechanism_name"].lower() or q in m["ontology_description"].lower():
            return m
    return None


def cmd_target_candidates(query):
    drugs, mechanisms, coverage, gaps, korean = load_all()
    m = _find_mechanism(query, mechanisms)
    if m is None:
        print("ERROR: mechanism '%s' not found." % query)
        sys.exit(2)
    print(DISCLAIMER)
    print()
    print("Mechanism : %s (%s)" % (m["mechanism_name"], m["mechanism_id"]))
    print("Category  : %s" % m["mechanism_category"])
    print("Pathway   : KEGG %s | Reactome %s | MeSH %s" % (m["kegg_pathway"], m["reactome_id"], m["mesh_id"]))
    print("Ontology  : %s" % m["ontology_description"])
    print()
    g = next((x for x in gaps if x["mechanism_id"] == m["mechanism_id"]), None)
    if g is None:
        print("Gap row 미발견 — coverage matrix만 표시.")
    else:
        print("현재 attack 약물 수 : %s" % g["current_attack_drug_count"])
        print("Phase 3+ 약물       : %s" % g["phase_3_count"])
        print("Gap score           : %s / 10" % g["gap_score"])
        print()
        print("차세대 후보:")
        for cand in g["next_gen_target_candidate_list"].split("|"):
            print("  - %s" % cand.strip())
        print()
        print("Preclinical evidence:")
        print("  - PubMed 논문 수      : %s" % g["preclinical_pubmed_n"])
        print("  - KO mouse 표현형     : %s" % g["ko_mouse_phenotype"])
        print("  - Human GWAS 연관     : %s" % g["human_gwas_association"])
    print()
    # Current drugs covering this mechanism
    drug_by_id = {d["drug_id"]: d for d in drugs}
    print("이 mechanism 을 현재 attack 중인 약물:")
    found = False
    for c in coverage:
        if c["mechanism_id"] == m["mechanism_id"]:
            d = drug_by_id.get(c["drug_id"], {})
            print("  - %s (%s) | phase=%s | score=%s | %s" % (
                d.get("drug_name", "?"), c["drug_id"],
                d.get("phase", "?"), c["coverage_score"], c["evidence_type"]
            ))
            found = True
    if not found:
        print("  (없음 — 완전 미개척)")


def _find_korean_row(target_name, korean):
    q = target_name.strip().lower()
    for row in korean:
        if q in row["target_candidate"].lower() or row["target_candidate"].lower() in q:
            return row
    return None


def cmd_korean_poc(target_name):
    drugs, mechanisms, coverage, gaps, korean = load_all()
    row = _find_korean_row(target_name, korean)
    if row is None:
        print("ERROR: Korean PoC row for '%s' not found." % target_name)
        print("사용 가능한 candidates:")
        for r in korean:
            print("  - %s" % r["target_candidate"])
        sys.exit(2)
    print(DISCLAIMER)
    print()
    print("=" * 60)
    print("Korean MASH cohort PoC suitability: %s" % row["target_candidate"])
    print("=" * 60)
    print("Subphenotype 적합도 : %s" % row["korean_subphenotype_fit"])
    print("예상 N              : %s" % row["estimated_n"])
    print("기간                : %s 개월" % row["expected_duration_months"])
    print("Endpoint            : %s" % row["expected_endpoint"])
    print("KASL NIT 호환       : %s" % row["kasl_nit_compatible"])
    print("후보 사이트         : %s" % row["candidate_sites"])
    print("한국 GWAS allele freq:")
    print("  - PNPLA3 (rs738409 G) : %s" % row["pnpla3_korean_aaf"])
    print("  - TM6SF2 (rs58542926 T): %s" % row["tm6sf2_korean_aaf"])


def _grant_card(target_name, drugs, mechanisms, coverage, gaps, korean):
    # find mechanism by best fuzzy match through gap row or mechanism name
    target_lower = target_name.strip().lower()
    g_match = None
    m_match = None
    for m in mechanisms:
        if target_lower in m["mechanism_name"].lower() or m["mechanism_name"].lower() in target_lower:
            m_match = m
            break
    if m_match is None:
        # try gap-row candidate strings
        for g in gaps:
            if target_lower in g["next_gen_target_candidate_list"].lower():
                m_match = next((x for x in mechanisms if x["mechanism_id"] == g["mechanism_id"]), None)
                g_match = g
                break
    if m_match is None:
        return None
    if g_match is None:
        g_match = next((x for x in gaps if x["mechanism_id"] == m_match["mechanism_id"]), {})
    k_row = _find_korean_row(target_name, korean)
    if k_row is None and m_match is not None:
        # try matching by mechanism name
        k_row = _find_korean_row(m_match["mechanism_name"], korean)

    # build narrative
    narrative = ("%s 은(는) MASH 섬유화 진행의 %s 카테고리 핵심 mechanism 으로, "
                 "현재 phase 3 도달 약물이 부족한 미충족 영역.") % (m_match["mechanism_name"], m_match["mechanism_category"])

    # Split candidates into antibodies / small molecules heuristic
    cands = (g_match.get("next_gen_target_candidate_list", "") or "").split("|") if isinstance(g_match, dict) else []
    abs_, sms = [], []
    for c in cands:
        cl = c.lower()
        if "mab" in cl or "antisense" in cl or "aso" in cl or "fc" in cl or "fusion" in cl:
            abs_.append(c.strip())
        else:
            sms.append(c.strip())

    lines = []
    lines.append("# %s 차세대 MASH 약물 IIT/grant proposal card" % m_match["mechanism_name"])
    lines.append("")
    lines.append("## Mechanism narrative")
    lines.append(narrative)
    lines.append("")
    lines.append("## 현재 coverage 상태")
    lines.append("- Attack 약물 수: %s" % g_match.get("current_attack_drug_count", "?"))
    lines.append("- Phase 3+: %s" % g_match.get("phase_3_count", "?"))
    lines.append("- Gap score: %s/10 (높을수록 미충족)" % g_match.get("gap_score", "?"))
    lines.append("")
    lines.append("## Preclinical evidence")
    lines.append("- PubMed 논문 수: %s" % g_match.get("preclinical_pubmed_n", "?"))
    lines.append("- KO mouse 표현형: %s" % g_match.get("ko_mouse_phenotype", "?"))
    lines.append("- Human GWAS 연관: %s" % g_match.get("human_gwas_association", "?"))
    lines.append("")
    lines.append("## 차세대 후보 (사용 가능)")
    lines.append("- 항체/생물학적제제: %s" % (", ".join(abs_) if abs_ else "(추가 탐색 필요)"))
    lines.append("- 소분자: %s" % (", ".join(sms) if sms else "(추가 탐색 필요)"))
    lines.append("- Patent landscape: 합성 데이터 (실제 검토 필요)")
    lines.append("")
    lines.append("## Korean cohort PoC plan")
    if k_row:
        lines.append("- 한국 subphenotype 적합도: %s" % k_row["korean_subphenotype_fit"])
        lines.append("- 예상 N: %s" % k_row["estimated_n"])
        lines.append("- 기간: %s개월" % k_row["expected_duration_months"])
        lines.append("- Endpoint: %s" % k_row["expected_endpoint"])
        lines.append("- KASL NIT 호환: %s" % k_row["kasl_nit_compatible"])
        lines.append("- 후보 사이트: %s" % k_row["candidate_sites"])
        lines.append("- 한국 GWAS allele frequency: PNPLA3 %s, TM6SF2 %s" % (
            k_row["pnpla3_korean_aaf"], k_row["tm6sf2_korean_aaf"]))
    else:
        lines.append("- Korean PoC row 미등록 — 추가 데이터 입력 필요")
    lines.append("")
    lines.append("## Grant target")
    lines.append("- KDDF 신약개발 분야: 적합")
    lines.append("- 국가신약개발사업단: 적합")
    lines.append("- NRF 중견/우수 연구: 적합")
    lines.append("- KFDA/FDA accelerated approval pathway: NIT-based surrogate endpoint + 가속승인 추진 가능")
    lines.append("")
    lines.append("## Abstract template suggestion")
    lines.append("- KASL: '%s 표적 차세대 MASH 약물 후보의 한국 코호트 PoC 가능성 분석'" % m_match["mechanism_name"])
    lines.append("- AASLD: 'Next-generation %s targeting in MASH fibrosis: a Korean cohort PoC feasibility'" % m_match["mechanism_name"])
    lines.append("- EASL ILC: 'Underdrugged %s axis in MASH fibrogenesis: opportunities for Korean phenotype-enriched trials'" % m_match["mechanism_name"])
    lines.append("")
    lines.append("## OpenClaw 차세대 선별 layer JSON ready")
    lines.append("")
    lines.append("## ⚠️ 디스클레이머")
    lines.append("본 도구는 연구용·참고용 grant proposal hypothesis 생성기입니다. 실제 약물 개발·임상의사결정·승인 결정에 사용 금지.")
    return "\n".join(lines)


def cmd_grant_card(target):
    drugs, mechanisms, coverage, gaps, korean = load_all()
    if not target:
        # default = top 1 gap
        ranked = _gap_rank(gaps, mechanisms)
        if not ranked:
            print("ERROR: gap targets empty.")
            sys.exit(2)
        target = ranked[0]["mechanism_name"]
        print("(--target 미지정 → Top 1 gap mechanism '%s' 자동 선택)\n" % target)
    card = _grant_card(target, drugs, mechanisms, coverage, gaps, korean)
    if card is None:
        print("ERROR: cannot build grant card for '%s'." % target)
        sys.exit(2)
    print(card)


def cmd_export_openclaw(out_path):
    drugs, mechanisms, coverage, gaps, korean = load_all()
    ranked = _gap_rank(gaps, mechanisms)
    payload = {
        "schema": "openclaw_bio_pivot.v1",
        "domain": "MASLD/MASH-fibrosis",
        "generated_by": "MASHFibroTargetGap-Kor",
        "disclaimer": DISCLAIMER,
        "drugs": drugs,
        "mechanisms": mechanisms,
        "coverage": coverage,
        "gap_targets_ranked": ranked,
        "korean_poc": korean,
    }
    with open(out_path, "w", encoding="utf-8") as f:
        json.dump(payload, f, ensure_ascii=False, indent=2)
    print(DISCLAIMER)
    print()
    print("OpenClaw export → %s" % out_path)
    print("Drugs=%d | Mechanisms=%d | Coverage cells=%d | Gap rows=%d | Korean PoC=%d" % (
        len(drugs), len(mechanisms), len(coverage), len(ranked), len(korean)))


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def build_parser():
    p = argparse.ArgumentParser(
        prog="mash-fibro-target-gap-kor",
        description=(
            "MASHFibroTargetGap-Kor (매시파이브로타겟갭코어): MASH fibrosis "
            "drug-target landscape gap mining + Korean cohort PoC + 한국어 "
            "grant proposal card.\n\n"
            + DISCLAIMER
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument("--summary", action="store_true", help="도구 개요 요약")
    p.add_argument("--pipeline", action="store_true", help="MASH 약물 pipeline 표")
    p.add_argument("--coverage-matrix", action="store_true", help="drug × mechanism coverage matrix 출력")
    p.add_argument("--gap-rank", action="store_true", help="미충족 mechanism gap ranking")
    p.add_argument("--top", type=int, default=10, help="--gap-rank Top N (default 10)")
    p.add_argument("--target-candidates", type=str, default=None, metavar="MECH",
                   help="특정 mechanism gap 의 차세대 target 후보 + preclinical evidence")
    p.add_argument("--korean-poc", type=str, default=None, metavar="TARGET",
                   help="특정 target 의 Korean cohort PoC suitability 평가")
    p.add_argument("--grant-card", action="store_true",
                   help="한국어 grant/IIT proposal card 생성 (--target 지정 가능)")
    p.add_argument("--target", type=str, default=None, metavar="NAME",
                   help="--grant-card 대상 target/mechanism name")
    p.add_argument("--export-openclaw", type=str, default=None, metavar="OUT.json",
                   help="OpenClaw 약물 재조합 DB JSON export")
    return p


def main(argv=None):
    parser = build_parser()
    args = parser.parse_args(argv)

    actions = [args.summary, args.pipeline, args.coverage_matrix, args.gap_rank,
               args.target_candidates, args.korean_poc, args.grant_card, args.export_openclaw]
    if not any(actions):
        parser.print_help()
        print()
        print(DISCLAIMER)
        return 0

    if args.summary:
        cmd_summary()
    if args.pipeline:
        cmd_pipeline()
    if args.coverage_matrix:
        cmd_coverage_matrix()
    if args.gap_rank:
        cmd_gap_rank(args.top)
    if args.target_candidates:
        cmd_target_candidates(args.target_candidates)
    if args.korean_poc:
        cmd_korean_poc(args.korean_poc)
    if args.grant_card:
        cmd_grant_card(args.target)
    if args.export_openclaw:
        cmd_export_openclaw(args.export_openclaw)
    return 0


if __name__ == "__main__":
    sys.exit(main())
