"""6-design effect grid + concordance score.

방향 일치(<1.0 vs >1.0) + CI overlap + design 가중치를 통합한
concordance score [0,1]을 계산한다.
"""
from __future__ import annotations

import math
from typing import Dict, List, Optional, Tuple

from .ontology import load_effects

# design 가중치: triangulation Lawlor 기준
# RCT highest, MVMR > BMI_MR, bariatric_natural strong (n=large + long FU),
# within_subject_crossover medium, observational confounded, DIO_animal low
DESIGN_WEIGHT = {
    "RCT": 1.0,
    "multivariable_MR": 0.85,
    "BMI_MR": 0.75,
    "bariatric_natural": 0.80,
    "within_subject_crossover": 0.65,
    "observational": 0.55,
    "DIO_animal": 0.40,
}


def _to_float(s: str) -> Optional[float]:
    try:
        return float(s)
    except (ValueError, TypeError):
        return None


def get_pair_rows(intervention: str, outcome: str,
                  effects: Optional[List[Dict[str, str]]] = None) -> List[Dict[str, str]]:
    effects = effects if effects is not None else load_effects()
    return [r for r in effects
            if r["intervention"] == intervention and r["outcome"] == outcome]


def direction(estimate: float) -> int:
    """1: harm, -1: protect, 0: null (CI 무관 단순 점추정 기반)."""
    if estimate is None:
        return 0
    if estimate < 0.95:
        return -1
    if estimate > 1.05:
        return 1
    return 0


def ci_overlap(a_low: float, a_high: float, b_low: float, b_high: float) -> bool:
    return not (a_high < b_low or b_high < a_low)


def concordance_score(rows: List[Dict[str, str]]) -> Dict:
    """6-design 방향 일치율 + CI overlap fraction + design 다양성을 결합.

    Returns: dict with score (0~1), direction_agreement, ci_overlap_frac,
             n_designs, design_diversity.
    """
    if not rows:
        return {"score": 0.0, "direction_agreement": None, "ci_overlap_frac": None,
                "n_designs": 0, "design_diversity": 0, "rows_used": 0}

    parsed = []
    for r in rows:
        est = _to_float(r.get("effect_estimate"))
        lo = _to_float(r.get("ci_low"))
        hi = _to_float(r.get("ci_high"))
        if est is None:
            continue
        parsed.append({"design": r["design"], "est": est, "lo": lo, "hi": hi,
                       "w": DESIGN_WEIGHT.get(r["design"], 0.5)})

    if not parsed:
        return {"score": 0.0, "direction_agreement": None, "ci_overlap_frac": None,
                "n_designs": 0, "design_diversity": 0, "rows_used": 0}

    dirs = [direction(p["est"]) for p in parsed]
    # weighted majority direction
    score_by_dir = {-1: 0.0, 0: 0.0, 1: 0.0}
    total_w = 0.0
    for p, d in zip(parsed, dirs):
        score_by_dir[d] += p["w"]
        total_w += p["w"]
    majority_dir = max(score_by_dir, key=score_by_dir.get)
    direction_agreement = score_by_dir[majority_dir] / total_w if total_w > 0 else 0.0

    # CI pairwise overlap fraction
    pairs = 0
    overlapping = 0
    for i in range(len(parsed)):
        for j in range(i + 1, len(parsed)):
            a, b = parsed[i], parsed[j]
            if None in (a["lo"], a["hi"], b["lo"], b["hi"]):
                continue
            pairs += 1
            if ci_overlap(a["lo"], a["hi"], b["lo"], b["hi"]):
                overlapping += 1
    ci_overlap_frac = (overlapping / pairs) if pairs else None

    n_designs = len(set(p["design"] for p in parsed))
    # 6-design max diversity bonus
    design_diversity = n_designs / 6.0

    # combined score: 50% direction, 30% CI overlap, 20% diversity
    ci_component = ci_overlap_frac if ci_overlap_frac is not None else direction_agreement
    score = 0.5 * direction_agreement + 0.3 * ci_component + 0.2 * design_diversity
    score = max(0.0, min(1.0, score))

    return {
        "score": round(score, 3),
        "direction_agreement": round(direction_agreement, 3),
        "ci_overlap_frac": round(ci_overlap_frac, 3) if ci_overlap_frac is not None else None,
        "n_designs": n_designs,
        "design_diversity": round(design_diversity, 3),
        "rows_used": len(parsed),
        "majority_direction": {-1: "protect", 0: "null", 1: "harm"}[majority_dir],
    }


def build_grid() -> List[Dict]:
    """모든 (intervention, outcome) 쌍의 concordance grid."""
    effects = load_effects()
    pairs = {}
    for r in effects:
        pairs.setdefault((r["intervention"], r["outcome"]), []).append(r)
    out = []
    for (iv, oc), rows in pairs.items():
        score = concordance_score(rows)
        out.append({
            "intervention": iv,
            "outcome": oc,
            "outcome_category": rows[0].get("outcome_category", ""),
            **score,
        })
    out.sort(key=lambda x: (-x["score"], x["intervention"]))
    return out


def find_discordant(top_n: int = 10) -> List[Dict]:
    """direction agreement 낮은 쌍 = discordance 의심."""
    grid = build_grid()
    discordant = [g for g in grid
                  if g.get("direction_agreement") is not None
                  and g["direction_agreement"] < 0.7
                  and g["n_designs"] >= 2]
    discordant.sort(key=lambda x: x["direction_agreement"])
    return discordant[:top_n]


if __name__ == "__main__":
    grid = build_grid()
    print(f"grid pairs: {len(grid)}")
    for g in grid[:5]:
        print(g)
    print("--- discordant ---")
    for d in find_discordant(5):
        print(d)
