import streamlit as st
import os
import json
import pandas as pd
import plotly.express as px

from VerifierApp.config import RUNS_DIR
from VerifierApp.core.runs_db import (
    load_runs_df, rebuild_index,
    load_llm_summary_df, load_llm_model_summary_df, load_milestone_summary_df,
)
from VerifierApp.localisation_module import t, ICONS


@st.cache_data(ttl=20, show_spinner=False)
def _cached_load_runs_df(runs_dir: str) -> "pd.DataFrame":
    """Cached wrapper — re-queries the DB at most once every 20 seconds."""
    return load_runs_df(runs_dir)


@st.cache_data(ttl=30, show_spinner=False)
def _cached_load_llm_summary(runs_dir: str) -> "pd.DataFrame":
    return load_llm_summary_df(runs_dir)


@st.cache_data(ttl=30, show_spinner=False)
def _cached_load_llm_model_summary(runs_dir: str) -> "pd.DataFrame":
    return load_llm_model_summary_df(runs_dir)


@st.cache_data(ttl=30, show_spinner=False)
def _cached_load_milestone_summary(runs_dir: str) -> "pd.DataFrame":
    return load_milestone_summary_df(runs_dir)


@st.cache_data(ttl=60, show_spinner=False)
def _cached_load_failed_phenotypes(runs_dir: str, files: tuple) -> "pd.DataFrame":
    """Load failed phenotypes from JSON files. Cached per unique file list."""
    rows = []
    for r in files:
        fpath = os.path.join(runs_dir, r)
        if not os.path.exists(fpath):
            continue
        try:
            with open(fpath, "r", encoding="utf-8-sig") as f:
                rj = json.load(f)
            raw = rj.get("results", [])
            if isinstance(raw, dict):
                results = raw.get("results", [])
                att_details = raw.get("model_attractors_details", [])
            elif isinstance(raw, list):
                results = raw
                att_details = []
            else:
                continue
            llm = rj.get("llm_used", "Unknown")
            for item in results:
                if isinstance(item, dict) and not item.get("verified"):
                    rows.append({
                        "_llm":       llm,
                        "_phenotype": item.get("phenotype", "Unknown"),
                        "_rationale": item.get("rationale", ""),
                        "_variables": str(item.get("variables", {})),
                        "_attractors": json.dumps(att_details, indent=2),
                        "_file":      r,
                    })
        except Exception:
            pass
    return pd.DataFrame(rows)


def _clear_all_caches():
    _cached_load_runs_df.clear()
    _cached_load_llm_summary.clear()
    _cached_load_llm_model_summary.clear()
    _cached_load_milestone_summary.clear()
    _cached_load_failed_phenotypes.clear()


def render_history():
    st.header(f"{ICONS['nav_history']} {t('history_header')}")

    COL_DATETIME        = t("history_col_datetime")
    COL_LLM             = t("history_col_llm")
    COL_INPUT           = t("history_col_input")
    COL_AEON            = t("history_col_aeon")
    COL_ATTRACTORS      = t("history_col_attractors")
    COL_PHENOTYPES      = t("history_col_phenotypes")
    COL_VERIFIED        = t("history_col_verified")
    COL_SUCCESS_RATE    = t("history_col_success_rate")
    COL_COVERAGE        = t("history_col_coverage")
    COL_TOKENS          = t("history_col_tokens")
    COL_MILESTONE       = t("history_col_milestone")
    COL_FILE            = t("history_col_file")
    COL_ROW_COUNT       = t("history_col_run_count")
    COL_CORRECT_RUNS    = t("history_col_correct_runs")
    COL_INCORRECT_RUNS  = t("history_col_incorrect_runs")
    COL_AVERAGE_TOKENS  = t("history_col_avg_tokens")

    FAIL_COL_LLM        = t("history_fail_col_llm")
    FAIL_COL_PHENOTYPE  = t("history_fail_col_phenotype")
    FAIL_COL_RATIONALE  = t("history_fail_col_rationale")
    FAIL_COL_VARIABLES  = t("history_fail_col_variables")
    FAIL_COL_FILE       = t("history_fail_col_file")

    col_refresh, col_rebuild = st.columns([1, 3])
    with col_refresh:
        if st.button(f"{ICONS['run']} {t('history_refresh_btn')}", width='stretch'):
            _clear_all_caches()
            st.rerun()
    with col_rebuild:
        with st.expander(f"{ICONS['log']} {t('history_rebuild_index_label')}", expanded=False):
            st.caption(t("history_rebuild_index_help"))
            if st.button(t("history_rebuild_index_btn"), icon=ICONS["save"]):
                with st.spinner(t("history_rebuild_index_spinner")):
                    n = rebuild_index(RUNS_DIR)
                _clear_all_caches()
                st.success(t("history_rebuild_index_done", n=n))
                st.rerun()

    raw_df = _cached_load_runs_df(RUNS_DIR)

    if raw_df.empty:
        st.info(t("history_no_runs"))
        return

    df = raw_df.rename(columns={
        "timestamp":              COL_DATETIME,
        "llm_used":               COL_LLM,
        "input_group":            COL_INPUT,
        "model_group":            COL_AEON,
        "model_attractors_count": COL_ATTRACTORS,
        "total_phenotypes":       COL_PHENOTYPES,
        "verified_count":         COL_VERIFIED,
        "success_rate":           COL_SUCCESS_RATE,
        "coverage":               COL_COVERAGE,
        "total_tokens":           COL_TOKENS,
        "milestone":              COL_MILESTONE,
        "file":                   COL_FILE,
    })
    df[COL_COVERAGE]   = df[COL_COVERAGE].where(df[COL_COVERAGE].notna(), other="N/A")
    df[COL_ATTRACTORS] = df[COL_ATTRACTORS].where(df[COL_ATTRACTORS].notna(), other="N/A")
    df[COL_TOKENS]     = pd.array(
        pd.to_numeric(df[COL_TOKENS], errors="coerce"), dtype=pd.Int64Dtype()
    )

    all_milestones = sorted(df[COL_MILESTONE].dropna().unique().tolist())
    milestone_options = [t("history_milestone_all")] + [
        f"{t('history_milestone_prefix')} {m}" for m in all_milestones
    ]
    selected_milestone_label = st.selectbox(
        t("history_milestone_label"), milestone_options, key="history_milestone_filter"
    )
    is_all_milestones = selected_milestone_label == t("history_milestone_all")

    if not is_all_milestones:
        selected_milestone_num = int(selected_milestone_label.split()[-1])
        df = df[df[COL_MILESTONE] == selected_milestone_num]

    st.subheader(t("history_overview"))
    col1, col2, col3, col4, col5 = st.columns(5)
    col1.metric(t("history_total_runs"), len(df))
    col2.metric(t("history_extracted"), df[COL_PHENOTYPES].sum())
    col3.metric(t("history_verified_total"), df[COL_VERIFIED].sum())

    total_phenotypes_sum = df[COL_PHENOTYPES].sum()
    celkova_uspesnost = (
        df[COL_VERIFIED].sum() / total_phenotypes_sum * 100
        if total_phenotypes_sum > 0 else 0
    )
    col4.metric(t("history_success_rate"), f"{round(celkova_uspesnost, 1)} %")

    valid_coverage = pd.to_numeric(df[COL_COVERAGE], errors='coerce').dropna()
    if not valid_coverage.empty:
        pokrytie = valid_coverage.mean()
        col5.metric(t("history_coverage"), f"{round(pokrytie, 1)} %", help=t("history_coverage_help"))
    else:
        col5.metric(t("history_coverage"), "N/A", help=t("history_coverage_na_help"))

    st.markdown("---")

    tab_stats, tab_failures, tab_details = st.tabs([
        t("history_tab_stats"), t("history_tab_diagnostics"), t("history_tab_details")
    ])

    # ══════════════════════════════════════════════════════════════════════════
    with tab_stats:

        # ── LLM overview table ────────────────────────────────────────────────
        st.subheader(t("history_llm_table_header"))
        group_by_model = st.toggle(
            t("history_llm_table_group_by_model"),
            value=False,
            key="llm_table_group_by_model",
            help=t("history_llm_table_group_by_model_help"),
        )

        if is_all_milestones:
            # ── Fast path: use precomputed summary tables ──────────────────
            if group_by_model:
                raw_s = _cached_load_llm_model_summary(RUNS_DIR)
                llm_overview_df = raw_s.rename(columns={
                    "llm_used":         COL_LLM,
                    "model_group":      COL_AEON,
                    "total_runs":       COL_ROW_COUNT,
                    "correct_runs":     COL_CORRECT_RUNS,
                    "avg_success_rate": COL_SUCCESS_RATE,
                    "avg_coverage":     COL_COVERAGE,
                    "avg_tokens":       COL_AVERAGE_TOKENS,
                })
                llm_overview_df[COL_INCORRECT_RUNS] = (
                    llm_overview_df[COL_ROW_COUNT] - llm_overview_df[COL_CORRECT_RUNS]
                )
                ordered_cols = [COL_LLM, COL_AEON, COL_ROW_COUNT, COL_CORRECT_RUNS,
                                COL_INCORRECT_RUNS, COL_SUCCESS_RATE, COL_COVERAGE, COL_AVERAGE_TOKENS]
            else:
                raw_s = _cached_load_llm_summary(RUNS_DIR)
                llm_overview_df = raw_s.rename(columns={
                    "llm_used":         COL_LLM,
                    "total_runs":       COL_ROW_COUNT,
                    "correct_runs":     COL_CORRECT_RUNS,
                    "avg_success_rate": COL_SUCCESS_RATE,
                    "avg_coverage":     COL_COVERAGE,
                    "avg_tokens":       COL_AVERAGE_TOKENS,
                })
                llm_overview_df[COL_INCORRECT_RUNS] = (
                    llm_overview_df[COL_ROW_COUNT] - llm_overview_df[COL_CORRECT_RUNS]
                )
                ordered_cols = [COL_LLM, COL_ROW_COUNT, COL_CORRECT_RUNS,
                                COL_INCORRECT_RUNS, COL_SUCCESS_RATE, COL_COVERAGE, COL_AVERAGE_TOKENS]

            llm_overview_df[COL_AVERAGE_TOKENS] = (
                pd.to_numeric(llm_overview_df[COL_AVERAGE_TOKENS], errors="coerce")
                .round(0).astype("Int64")
            )
            llm_overview_df = llm_overview_df[ordered_cols].sort_values(
                COL_ROW_COUNT, ascending=False
            ).reset_index(drop=True)

        else:
            _ov = df.copy()
            _ov["_cov_num"] = pd.to_numeric(_ov[COL_COVERAGE], errors='coerce').fillna(0)
            _ov["_correct"] = ((_ov[COL_SUCCESS_RATE] >= 100) & (_ov["_cov_num"] >= 100)).astype(int)
            _ov["_tokens"]  = pd.to_numeric(_ov[COL_TOKENS], errors='coerce')

            group_cols = [COL_LLM, COL_AEON] if group_by_model else [COL_LLM]
            llm_overview_df = (
                _ov.groupby(group_cols)
                .agg(
                    _total       =(COL_SUCCESS_RATE, 'count'),
                    _correct     =("_correct",       'sum'),
                    _success_rate=(COL_SUCCESS_RATE, 'mean'),
                    _coverage    =("_cov_num",       'mean'),
                    _avg_tokens  =("_tokens",        'mean'),
                )
                .reset_index()
            )
            llm_overview_df["_other"]      = llm_overview_df["_total"] - llm_overview_df["_correct"]
            llm_overview_df["_success_rate"] = llm_overview_df["_success_rate"].round(1)
            llm_overview_df["_coverage"]     = llm_overview_df["_coverage"].round(1)
            llm_overview_df["_avg_tokens"]   = llm_overview_df["_avg_tokens"].round(0).astype("Int64")
            llm_overview_df = llm_overview_df.rename(columns={
                "_total":        COL_ROW_COUNT,
                "_correct":      COL_CORRECT_RUNS,
                "_other":        COL_INCORRECT_RUNS,
                "_success_rate": COL_SUCCESS_RATE,
                "_coverage":     COL_COVERAGE,
                "_avg_tokens":   COL_AVERAGE_TOKENS,
            })
            ordered_cols = [COL_LLM]
            if group_by_model:
                ordered_cols.append(COL_AEON)
            ordered_cols += [COL_ROW_COUNT, COL_CORRECT_RUNS, COL_INCORRECT_RUNS,
                             COL_SUCCESS_RATE, COL_COVERAGE, COL_AVERAGE_TOKENS]
            llm_overview_df = (
                llm_overview_df[ordered_cols]
                .sort_values(COL_ROW_COUNT, ascending=False)
                .reset_index(drop=True)
            )

        st.dataframe(llm_overview_df, hide_index=True, width='stretch')
        st.markdown("---")

        # ── Charts ────────────────────────────────────────────────────────────
        col_g1, col_g2 = st.columns(2)

        if is_all_milestones:
            # Use the precomputed LLM summary (already loaded above as raw_s)
            chart_base = _cached_load_llm_summary(RUNS_DIR).copy()
            chart_base = chart_base.rename(columns={
                "llm_used":         COL_LLM,
                "avg_success_rate": COL_SUCCESS_RATE,
                "avg_coverage":     COL_COVERAGE,
            })
        else:
            # Compute from filtered df
            _sc  = df.groupby(COL_LLM)[COL_SUCCESS_RATE].mean().reset_index()
            _cdf = df.copy()
            _cdf[COL_COVERAGE] = pd.to_numeric(_cdf[COL_COVERAGE], errors='coerce').fillna(0)
            _cc  = _cdf.groupby(COL_LLM)[COL_COVERAGE].mean().reset_index()
            chart_base = pd.merge(_sc, _cc, on=COL_LLM)

        with col_g1:
            st.subheader(t("history_chart_success_by_llm"))
            sc_data = chart_base[[COL_LLM, COL_SUCCESS_RATE]].copy()
            sc_data[COL_SUCCESS_RATE] = pd.to_numeric(sc_data[COL_SUCCESS_RATE], errors='coerce').fillna(0)
            sc_data["_color"] = sc_data[COL_SUCCESS_RATE].apply(
                lambda v: "rgba(46, 204, 113, 0.8)"  if v >= 100
                else      "rgba(255, 165, 0, 1)"     if abs(v - 100) <= 20
                else      "rgba(231, 76, 60, 0.8)"
            )
            sc_data = sc_data.sort_values(COL_SUCCESS_RATE, ascending=False)
            fig_success = px.bar(sc_data, x=COL_LLM, y=COL_SUCCESS_RATE,
                                 color="_color", color_discrete_map="identity",
                                 category_orders={COL_LLM: sc_data[COL_LLM].tolist()})
            fig_success.update_layout(showlegend=False, xaxis_title=None,
                                      yaxis_title=t("history_chart_success_pct"),
                                      margin=dict(l=0, r=0, t=30, b=0))
            st.plotly_chart(fig_success, width='stretch')

        with col_g2:
            st.subheader(t("history_chart_coverage_by_llm"))
            cov_data = chart_base[[COL_LLM, COL_COVERAGE]].copy()
            cov_data[COL_COVERAGE] = pd.to_numeric(cov_data[COL_COVERAGE], errors='coerce')
            cov_data = cov_data[cov_data[COL_COVERAGE].notna() & (cov_data[COL_COVERAGE] > 0)]
            if not cov_data.empty:
                cov_data["_color"] = cov_data[COL_COVERAGE].apply(
                    lambda v: "rgba(46, 204, 113, 0.8)"  if v == 100
                    else      "rgba(255, 165, 0, 1)"     if abs(v - 100) <= 20
                    else      "rgba(231, 76, 60, 0.8)"
                )
                cov_data["_dist"] = (cov_data[COL_COVERAGE] - 100).abs()
                cov_data = cov_data.sort_values("_dist")
                fig_cov = px.bar(cov_data, x=COL_LLM, y=COL_COVERAGE,
                                 color="_color", color_discrete_map="identity",
                                 category_orders={COL_LLM: cov_data[COL_LLM].tolist()})
                fig_cov.update_layout(showlegend=False, xaxis_title=None,
                                      yaxis_title=t("history_chart_coverage_pct"),
                                      margin=dict(l=0, r=0, t=30, b=0))
                st.plotly_chart(fig_cov, width='stretch')
            else:
                st.info(t("history_chart_no_coverage"))

        st.markdown("---")

        st.subheader(t("history_milestone_summary_header"))

        if is_all_milestones:
            ms_raw = _cached_load_milestone_summary(RUNS_DIR)
            ms_display = ms_raw.rename(columns={
                "milestone":        COL_MILESTONE,
                "total_runs":       COL_ROW_COUNT,
                "total_phenotypes": COL_PHENOTYPES,
                "verified_count":   COL_VERIFIED,
                "avg_success_rate": COL_SUCCESS_RATE,
                "avg_coverage":     COL_COVERAGE,
                "total_tokens":     COL_TOKENS,
            })
            ms_display[COL_TOKENS] = pd.array(
                pd.to_numeric(ms_display[COL_TOKENS], errors="coerce"), dtype=pd.Int64Dtype()
            )
            st.dataframe(ms_display, hide_index=True, width='stretch')
        else:
            ms_agg = df.copy()
            ms_agg[COL_COVERAGE] = pd.to_numeric(ms_agg[COL_COVERAGE], errors='coerce')
            ms_agg[COL_TOKENS]   = pd.to_numeric(ms_agg[COL_TOKENS],   errors='coerce')
            milestone_summary = ms_agg.groupby(COL_MILESTONE).agg(**{
                COL_ROW_COUNT: (COL_LLM,         'count'),
                COL_PHENOTYPES:(COL_PHENOTYPES,   'sum'),
                COL_VERIFIED:  (COL_VERIFIED,     'sum'),
                COL_SUCCESS_RATE:(COL_SUCCESS_RATE,'mean'),
                COL_COVERAGE:  (COL_COVERAGE,     'mean'),
                COL_TOKENS:    (COL_TOKENS,       'sum'),
            }).reset_index()
            milestone_summary[COL_SUCCESS_RATE] = milestone_summary[COL_SUCCESS_RATE].round(1)
            milestone_summary[COL_COVERAGE]     = milestone_summary[COL_COVERAGE].round(1)
            milestone_summary[COL_TOKENS]       = pd.array(
                milestone_summary[COL_TOKENS], dtype=pd.Int64Dtype()
            )
            st.dataframe(milestone_summary, hide_index=True, width='stretch')

        st.markdown("---")

        show_runs = st.toggle(
            t("history_show_runs_toggle", n=len(df)),
            value=False,
            key="history_show_runs",
        )
        if show_runs:
            def highlight_table(frame):
                suc = pd.to_numeric(frame.get(COL_SUCCESS_RATE), errors='coerce').fillna(0)
                cov = pd.to_numeric(frame.get(COL_COVERAGE),     errors='coerce').fillna(0)
                is_green = (suc >= 100) & (cov >= 100)
                styles = pd.DataFrame('', index=frame.index, columns=frame.columns)
                green_idx = is_green[is_green].index
                red_idx   = is_green[~is_green].index
                if len(green_idx):
                    styles.loc[green_idx] = 'background-color: rgba(46, 204, 113, 0.2)'
                if len(red_idx):
                    styles.loc[red_idx]   = 'background-color: rgba(231, 76, 60, 0.2)'
                return styles

            display_df = df.drop(columns=[COL_FILE])
            has_token_data = display_df[COL_TOKENS].notna().any() if COL_TOKENS in display_df.columns else False
            if not has_token_data and COL_TOKENS in display_df.columns:
                display_df = display_df.drop(columns=[COL_TOKENS])
            styled_df = display_df.style.apply(highlight_table, axis=None)
            st.dataframe(styled_df, width='stretch')

            csv_bytes = df.drop(columns=[COL_FILE]).to_csv(index=False).encode("utf-8")
            st.download_button(
                label=t("history_download_runs_csv"),
                data=csv_bytes,
                file_name=f"runs_{pd.Timestamp.now().strftime('%Y%m%d')}.csv",
                mime="text/csv",
                icon=ICONS["download"],
            )

    with tab_failures:
        st.subheader(t("history_diag_header"))
        st.info(t("history_diag_info"))

        show_failures = st.toggle(
            t("history_show_failures_toggle"),
            value=False,
            key="history_show_failures",
        )
        if not show_failures:
            _ver = pd.to_numeric(df[COL_VERIFIED],   errors='coerce').fillna(0)
            _tot = pd.to_numeric(df[COL_PHENOTYPES], errors='coerce').fillna(0)
            failure_count = int((_tot - _ver).clip(lower=0).sum())
            st.caption(t("history_failures_hidden_hint", n=failure_count))
        else:
            def _load_failed_phenotypes(file_list):
                raw = _cached_load_failed_phenotypes(RUNS_DIR, tuple(file_list))
                if raw.empty:
                    return raw
                return raw.rename(columns={
                    "_llm":        FAIL_COL_LLM,
                    "_phenotype":  FAIL_COL_PHENOTYPE,
                    "_rationale":  FAIL_COL_RATIONALE,
                    "_variables":  FAIL_COL_VARIABLES,
                    "_attractors": "Attractors",
                    "_file":       FAIL_COL_FILE,
                })

            _ver = pd.to_numeric(df[COL_VERIFIED],   errors='coerce').fillna(0)
            _tot = pd.to_numeric(df[COL_PHENOTYPES], errors='coerce').fillna(0)
            files_with_failures = df[_ver < _tot][COL_FILE].tolist()

            df_failed = _load_failed_phenotypes(files_with_failures)

            if df_failed.empty:
                st.success(t("history_no_failures"))
            else:
                filter_all = t("history_filter_all")
                selected_fail_llm = st.selectbox(
                    t("history_filter_llm"),
                    [filter_all] + df_failed[FAIL_COL_LLM].unique().tolist()
                )

                df_filtered = (
                    df_failed[df_failed[FAIL_COL_LLM] == selected_fail_llm]
                    if selected_fail_llm != filter_all
                    else df_failed
                )

                diag_file_path = os.path.join(RUNS_DIR, "diagnostics_cache.json")

                if 'error_categories' not in st.session_state:
                    if os.path.exists(diag_file_path):
                        try:
                            with open(diag_file_path, 'r', encoding='utf-8') as f:
                                st.session_state.error_categories = json.load(f)
                        except Exception:
                            st.session_state.error_categories = {}
                    else:
                        st.session_state.error_categories = {}

                CAT_UNCLASSIFIED = t("history_diag_cat_unclassified")
                categories = [
                    CAT_UNCLASSIFIED,
                    t("history_diag_cat_hallucination"),
                    t("history_diag_cat_misinterpretation"),
                    t("history_diag_cat_discrepancy"),
                ]

                for index, row in df_filtered.iterrows():
                    err_id = f"{row[FAIL_COL_FILE]}_{row[FAIL_COL_PHENOTYPE]}"
                    current_cat = st.session_state.error_categories.get(err_id, CAT_UNCLASSIFIED)
                    row_icon = ICONS["classified"] if current_cat != CAT_UNCLASSIFIED else ICONS["unclassified"]

                    with st.expander(f"{row_icon} {row[FAIL_COL_LLM]} | {row[FAIL_COL_PHENOTYPE]}"):
                        col_txt, col_diag = st.columns([2, 1])

                        with col_txt:
                            st.markdown(f"{t('history_diag_rationale_label')} {row[FAIL_COL_RATIONALE]}")
                            st.markdown(f"{t('history_diag_extracted_label')} `{row[FAIL_COL_VARIABLES]}`")

                            with st.popover(f"{ICONS['search']} {t('history_diag_compare_popover')}"):
                                st.write(t("history_diag_compare_desc"))
                                try:
                                    attractors_json = json.loads(row.get("Attractors", "[]"))
                                    import ast
                                    try:
                                        ext_vars = ast.literal_eval(row[FAIL_COL_VARIABLES])
                                    except Exception:
                                        ext_vars = {}

                                    if not attractors_json:
                                        st.warning(t("history_diag_no_attractor_details"))
                                    else:
                                        attr_tabs = st.tabs([
                                            t("history_diag_attractor_tab", id=a.get('id', i + 1))
                                            for i, a in enumerate(attractors_json)
                                        ])
                                        for i, attr in enumerate(attractors_json):
                                            with attr_tabs[i]:
                                                attr_vars = attr.get("state", {})
                                                matches = {}
                                                incorrect = {}
                                                hallucinated = {}
                                                for k, v in ext_vars.items():
                                                    if k in attr_vars:
                                                        if attr_vars[k] == v:
                                                            matches[k] = v
                                                        else:
                                                            incorrect[k] = {"expected": attr_vars[k], "got": v}
                                                    else:
                                                        hallucinated[k] = v
                                                missing = {k: v for k, v in attr_vars.items() if k not in ext_vars}
                                                if matches:
                                                    st.success(t("history_diag_matches", n=len(matches)) + "\n" + "\n".join(
                                                        [f"- `{k}`: {v}" for k, v in matches.items()]))
                                                if incorrect:
                                                    st.error(t("history_diag_incorrect", n=len(incorrect)) + "\n" + "\n".join([
                                                        t("history_diag_incorrect_item", k=k, got=v['got'], expected=v['expected'])
                                                        for k, v in incorrect.items()]))
                                                if hallucinated:
                                                    st.warning(t("history_diag_hallucinated", n=len(hallucinated)) + "\n" + "\n".join(
                                                        [t("history_diag_hallucinated_item", k=k) for k in hallucinated]))
                                                with st.expander(t("history_diag_missing_expander", n=len(missing))):
                                                    st.write(t("history_diag_missing_desc"))
                                                    for k, v in missing.items():
                                                        st.write(f"- `{k}`: {v}")
                                except Exception as e:
                                    st.error(t("history_diag_compare_error", error=e))

                            st.caption(t("history_source_run", file=row[FAIL_COL_FILE]))

                            # Category selector
                            with col_diag:
                                sel_cat = st.selectbox(
                                    t("history_diag_classify_label"),
                                    categories,
                                    index=categories.index(current_cat) if current_cat in categories else 0,
                                    key=f"cat_{err_id}",
                                )
                                if sel_cat != current_cat:
                                    st.session_state.error_categories[err_id] = sel_cat
                                    with open(diag_file_path, 'w', encoding='utf-8') as f:
                                        json.dump(st.session_state.error_categories, f, indent=2)

                st.markdown("---")
                st.subheader(f"{ICONS['chart']} {t('history_diag_stats_header')}")

                cat_counts = {cat: 0 for cat in categories}
                for index, row in df_failed.iterrows():
                    err_id = f"{row[FAIL_COL_FILE]}_{row[FAIL_COL_PHENOTYPE]}"
                    cat = st.session_state.error_categories.get(err_id, CAT_UNCLASSIFIED)
                    cat_counts[cat if cat in cat_counts else CAT_UNCLASSIFIED] += 1

                COL_CATEGORY = t("history_diag_col_category")
                COL_COUNT    = t("history_diag_col_count")
                df_pie = pd.DataFrame({
                    COL_CATEGORY: list(cat_counts.keys()),
                    COL_COUNT:    list(cat_counts.values()),
                })

                col_pie, col_stats = st.columns([2, 1])
                with col_pie:
                    if df_pie[COL_COUNT].sum() > 0:
                        fig_pie = px.pie(df_pie, values=COL_COUNT, names=COL_CATEGORY,
                                         hole=0.4, color=COL_CATEGORY,
                                         color_discrete_sequence=px.colors.qualitative.Set2)
                        fig_pie.update_layout(margin=dict(t=20, b=20, l=0, r=0))
                        st.plotly_chart(fig_pie, width='stretch')
                    else:
                        st.info(t("history_diag_no_errors"))

                with col_stats:
                    st.write(t("history_diag_progress_label"))
                    total_errors = len(df_failed)
                    analyzed = total_errors - cat_counts.get(CAT_UNCLASSIFIED, 0)
                    st.progress(analyzed / total_errors if total_errors > 0 else 0)
                    st.write(t("history_diag_progress_count", analyzed=analyzed, total=total_errors))

                    if st.button(t("history_diag_download_btn"), icon=ICONS["download"], width='stretch'):
                        export_df = df_failed.copy()
                        export_col = t("history_diag_export_col")
                        export_df[export_col] = export_df.apply(
                            lambda r: st.session_state.error_categories.get(
                                f"{r[FAIL_COL_FILE]}_{r[FAIL_COL_PHENOTYPE]}", CAT_UNCLASSIFIED),
                            axis=1,
                        )
                        csv_diag = export_df.drop(columns=[FAIL_COL_FILE]).to_csv(index=False).encode('utf-8')
                        st.download_button(
                            label=t("history_diag_download_confirm"),
                            data=csv_diag,
                            file_name=t("history_diag_export_filename",
                                        date=pd.Timestamp.now().strftime('%Y%m%d')),
                            mime="text/csv",
                            width='stretch',
                        )

    with tab_details:
        st.subheader(t("history_details_header"))
        selected_run_file = st.selectbox(t("history_details_select_run"), df[COL_FILE].tolist())

        if selected_run_file:
            with open(os.path.join(RUNS_DIR, selected_run_file), 'r', encoding='utf-8-sig') as f:
                run_details = json.load(f)

            st.write(t("history_details_prompt_label"))
            st.info(run_details.get("prompt", "—"))

            tokens_info = run_details.get("tokens_used", {})
            if tokens_info and isinstance(tokens_info, dict) and any(v is not None for v in tokens_info.values()):
                tc1, tc2, tc3 = st.columns(3)
                tc1.metric(t("history_tokens_prompt"),   tokens_info.get("request_tokens",  "N/A"))
                tc2.metric(t("history_tokens_response"), tokens_info.get("response_tokens", "N/A"))
                tc3.metric(t("history_tokens_total"),    tokens_info.get("total_tokens",    "N/A"))

            st.write(t("history_details_results_label"))
            raw_details = run_details.get("results", [])

            if isinstance(raw_details, str):
                st.error(t("history_details_error_msg"))
                st.code(raw_details)
                safe_items = []
            elif isinstance(raw_details, dict):
                safe_items = raw_details.get("results", [])
                if not isinstance(safe_items, list):
                    safe_items = []
            elif isinstance(raw_details, list):
                safe_items = raw_details
            else:
                safe_items = []

            for item in safe_items:
                if isinstance(item, dict):
                    if item.get("verified"):
                        status = f"{ICONS['verified']} {t('testing_verified')}"
                    else:
                        status = f"{ICONS['not_verified']} {t('testing_not_verified')}"
                    with st.expander(f"{status} - {item.get('phenotype', t('history_unknown_phenotype'))}"):
                        st.json(item)
                else:
                    st.warning(t("history_details_nonstandard", item=item))

