from __future__ import annotations

from typing import Any, Dict, Optional


def review_readiness_totals_template() -> Dict[str, int]:
    return {
        "needs_revision_job_count": 0,
        "open_critic_findings": 0,
        "high_severity_open_findings": 0,
        "open_review_comments": 0,
        "resolved_review_comments": 0,
        "acknowledged_review_comments": 0,
        "pending_review_comments": 0,
        "overdue_review_comments": 0,
        "stale_open_review_comments": 0,
        "linked_review_comments": 0,
        "orphan_linked_review_comments": 0,
        "low_confidence_citations": 0,
        "fallback_strategy_citations": 0,
    }


def accumulate_review_readiness_totals(totals: Dict[str, int], review_summary: Dict[str, Any]) -> None:
    if bool(review_summary.get("needs_revision")):
        totals["needs_revision_job_count"] += 1
    totals["open_critic_findings"] += int(review_summary.get("open_critic_findings") or 0)
    totals["high_severity_open_findings"] += int(review_summary.get("high_severity_open_findings") or 0)
    totals["open_review_comments"] += int(review_summary.get("open_review_comments") or 0)
    totals["resolved_review_comments"] += int(review_summary.get("resolved_review_comments") or 0)
    totals["acknowledged_review_comments"] += int(review_summary.get("acknowledged_review_comments") or 0)
    totals["pending_review_comments"] += int(review_summary.get("pending_review_comments") or 0)
    totals["overdue_review_comments"] += int(review_summary.get("overdue_review_comments") or 0)
    totals["stale_open_review_comments"] += int(review_summary.get("stale_open_review_comments") or 0)
    totals["linked_review_comments"] += int(review_summary.get("linked_review_comments") or 0)
    totals["orphan_linked_review_comments"] += int(review_summary.get("orphan_linked_review_comments") or 0)
    totals["low_confidence_citations"] += int(review_summary.get("low_confidence_citations") or 0)
    totals["fallback_strategy_citations"] += int(review_summary.get("fallback_strategy_citations") or 0)


def accumulate_stale_comment_bucket_counts(
    totals: Dict[str, int],
    review_summary: Dict[str, Any],
) -> None:
    comment_triage = (
        review_summary.get("comment_triage_summary")
        if isinstance(review_summary.get("comment_triage_summary"), dict)
        else {}
    )
    stale_bucket_counts = comment_triage.get("stale_comment_bucket_counts") if isinstance(comment_triage, dict) else {}
    if not isinstance(stale_bucket_counts, dict):
        return

    for bucket, count in stale_bucket_counts.items():
        token = str(bucket).strip().lower() or "general"
        totals[token] = int(totals.get(token) or 0) + int(count or 0)


def average_metric(metrics_rows: list[Dict[str, Any]], key: str) -> Optional[float]:
    values = [float(row[key]) for row in metrics_rows if isinstance(row.get(key), (int, float))]
    if not values:
        return None
    return round(sum(values) / len(values), 3)


def build_review_readiness_summary(
    totals: Dict[str, int],
    *,
    job_count: int,
    triage_summary: Dict[str, Any],
    stale_comment_bucket_counts: Dict[str, int],
) -> Dict[str, Any]:
    return {
        **totals,
        "needs_revision_rate": (round(totals["needs_revision_job_count"] / job_count, 4) if job_count else None),
        "open_critic_findings_per_job_avg": (
            round(totals["open_critic_findings"] / job_count, 4) if job_count else None
        ),
        "high_severity_open_findings_per_job_avg": (
            round(totals["high_severity_open_findings"] / job_count, 4) if job_count else None
        ),
        "open_review_comments_per_job_avg": (
            round(totals["open_review_comments"] / job_count, 4) if job_count else None
        ),
        "resolved_review_comments_per_job_avg": (
            round(totals["resolved_review_comments"] / job_count, 4) if job_count else None
        ),
        "acknowledged_review_comments_per_job_avg": (
            round(totals["acknowledged_review_comments"] / job_count, 4) if job_count else None
        ),
        "pending_review_comments_per_job_avg": (
            round(totals["pending_review_comments"] / job_count, 4) if job_count else None
        ),
        "overdue_review_comments_per_job_avg": (
            round(totals["overdue_review_comments"] / job_count, 4) if job_count else None
        ),
        "stale_open_review_comments_per_job_avg": (
            round(totals["stale_open_review_comments"] / job_count, 4) if job_count else None
        ),
        "linked_review_comments_per_job_avg": (
            round(totals["linked_review_comments"] / job_count, 4) if job_count else None
        ),
        "orphan_linked_review_comments_per_job_avg": (
            round(totals["orphan_linked_review_comments"] / job_count, 4) if job_count else None
        ),
        "triage_summary": triage_summary,
        "stale_comment_bucket_counts": dict(sorted(stale_comment_bucket_counts.items())),
    }


def merge_token_counts(target: Dict[str, int], source: Any, *, default_token: str = "unknown") -> None:
    if not isinstance(source, dict):
        return

    for key, count in source.items():
        token = str(key or "").strip().lower() or default_token
        target[token] = int(target.get(token) or 0) + _safe_int(count)


def overdue_row_sort_key(item: Dict[str, Any]) -> tuple[float, str]:
    overdue_hours = item.get("overdue_hours")
    overdue_hours_value = -1.0
    if isinstance(overdue_hours, (int, float, str)):
        try:
            overdue_hours_value = float(overdue_hours)
        except (TypeError, ValueError):
            overdue_hours_value = -1.0
    due_at = str(item.get("due_at") or "")
    return overdue_hours_value, due_at


def normalize_top_limit(top_limit: Any, *, default: int = 1) -> int:
    if isinstance(top_limit, bool):
        return max(1, int(top_limit))
    if isinstance(top_limit, int):
        return max(1, top_limit)
    if isinstance(top_limit, float):
        return max(1, int(top_limit))
    if isinstance(top_limit, str):
        token = top_limit.strip()
        if not token:
            return max(1, default)
        try:
            return max(1, int(token))
        except ValueError:
            try:
                return max(1, int(float(token)))
            except ValueError:
                return max(1, default)
    return max(1, default)


def _safe_int(value: Any, default: int = 0) -> int:
    if isinstance(value, bool):
        return int(value)
    if isinstance(value, int):
        return value
    if isinstance(value, float):
        return int(value)
    if isinstance(value, str):
        token = value.strip()
        if not token:
            return default
        try:
            return int(token)
        except ValueError:
            try:
                return int(float(token))
            except ValueError:
                return default
    return default
