# services/dashboard_service.py
import os
import json
from pathlib import Path
from typing import Dict, List, Any, Tuple

import pandas as pd
import numpy as np

# -------------------------
# Configurable paths
# -------------------------
BACKEND_DIR = Path(__file__).resolve().parents[1]   # .../Backend
DATA_DIR = Path(os.getenv("VITU_DATA_DIR", BACKEND_DIR / "data"))
CONV_DIR = Path(os.getenv("VITU_CONV_DIR", BACKEND_DIR / "conv"))

# Prefix lock (your requirement: show PA only for now)
ACTIVE_PREFIX = os.getenv("VITU_ACTIVE_PREFIX", "PA").upper()


# -------------------------
# Helpers
# -------------------------
def _date_folder(day_iso: str) -> Path:
    """ Backend/data/YYYY-MM-DD """
    return DATA_DIR / day_iso


def _load_json(p: Path, default: Any) -> Any:
    try:
        if p.exists():
            with p.open("r", encoding="utf-8") as f:
                return json.load(f)
    except Exception:
        pass
    return default


def _safe_float(x) -> float:
    try:
        return float(x)
    except Exception:
        return float("nan")


def _read_run_log_filtered(day_iso: str) -> pd.DataFrame:
    """
    Reads run_log.csv and filters rows by date + ACTIVE_PREFIX.
    Expected columns (best-effort): date, prefix, filename, pages, started, ended, durationSec, status, error
    """
    run_log_path = DATA_DIR / "run_log.csv"
    if not run_log_path.exists():
        return pd.DataFrame(columns=["date", "prefix", "filename", "pages", "started", "ended", "durationSec", "status", "error"])

    try:
        df = pd.read_csv(run_log_path)
    except Exception:
        return pd.DataFrame(columns=["date", "prefix", "filename", "pages", "started", "ended", "durationSec", "status", "error"])

    # Normalize columns
    for col in ["date", "prefix", "filename", "pages", "started", "ended", "durationSec", "status", "error"]:
        if col not in df.columns:
            df[col] = np.nan

    # Filter date and prefix
    df = df[df["date"].astype(str) == str(day_iso)]
    df["prefix"] = df["prefix"].astype(str).str.upper()
    df = df[df["prefix"] == ACTIVE_PREFIX]

    # Coerce numeric
    df["pages"] = pd.to_numeric(df["pages"], errors="coerce")
    df["durationSec"] = pd.to_numeric(df["durationSec"], errors="coerce")
    # Ensure string cols
    for c in ["filename", "status", "started", "ended", "error"]:
        df[c] = df[c].astype(str).fillna("")

    return df


# -------------------------
# Public API used by app.py
# -------------------------
def load_kpis_for_date(day_iso: str) -> Dict[str, Any]:
    """
    Returns: { received, processed, failed, pages, avgPages, avgTimeSec }
    Prefer snapshot JSON if present; otherwise compute from run_log.csv.
    """
    # Try snapshot first
    daily = _date_folder(day_iso)
    snap_path = daily / f"daily_metrics_{day_iso}.json"
    snap = _load_json(snap_path, default=None)

    if isinstance(snap, dict) and snap:
        # Trust snapshot, but enforce ints/floats
        return {
            "received": int(snap.get("received", 0) or 0),
            "processed": int(snap.get("processed", 0) or 0),
            "failed": int(snap.get("failed", 0) or 0),
            "pages": int(snap.get("pages", 0) or 0),
            "avgPages": float(snap.get("avgPages", 0.0) or 0.0),
            "avgTimeSec": float(snap.get("avgTimeSec", 0.0) or 0.0),
        }

    # Fallback: compute from run_log.csv
    df = _read_run_log_filtered(day_iso)
    if df.empty:
        return {"received": 0, "processed": 0, "failed": 0, "pages": 0, "avgPages": 0.0, "avgTimeSec": 0.0}

    # Received: distinct files
    received = df["filename"].nunique()

    # Processed/Failed by status
    processed = (df["status"].str.upper() == "SUCCESS").sum()
    failed = (df["status"].str.upper() == "FAILED").sum()

    # Pages & averages
    pages_total = int(pd.to_numeric(df["pages"], errors="coerce").fillna(0).sum())
    avg_pages = float(pd.to_numeric(df["pages"], errors="coerce").dropna().mean()) if received > 0 else 0.0
    avg_time = float(pd.to_numeric(df["durationSec"], errors="coerce").dropna().mean()) if received > 0 else 0.0

    return {
        "received": int(received or 0),
        "processed": int(processed or 0),
        "failed": int(failed or 0),
        "pages": pages_total,
        "avgPages": round(avg_pages, 2) if not np.isnan(avg_pages) else 0.0,
        "avgTimeSec": round(avg_time, 2) if not np.isnan(avg_time) else 0.0,
    }


def load_charts_for_date(day_iso: str) -> Dict[str, Any]:
    """
    Returns:
      {
        "status_pie": {"success": int, "failed": int},
        "time_hist": {"bin_edges": [..], "counts": [..]}
      }
    Prefers snapshot charts_<date>.json; otherwise computed from run_log.csv.
    """
    daily = _date_folder(day_iso)
    charts_path = daily / f"charts_{day_iso}.json"
    snap = _load_json(charts_path, default=None)

    if isinstance(snap, dict) and snap:
        # Ensure presence of keys, coerce types
        sp = snap.get("status_pie", {}) or {}
        th = snap.get("time_hist", {}) or {}
        edges = th.get("bin_edges") or []
        counts = th.get("counts") or []
        return {
            "status_pie": {
                "success": int(sp.get("success", 0) or 0),
                "failed": int(sp.get("failed", 0) or 0),
            },
            "time_hist": {
                "bin_edges": list(edges),
                "counts": list(counts),
            },
        }

    # Fallback: compute from run_log
    df = _read_run_log_filtered(day_iso)
    if df.empty:
        return {
            "status_pie": {"success": 0, "failed": 0},
            "time_hist": {"bin_edges": [0, 5, 10, 20, 30], "counts": [0, 0, 0, 0]},
        }

    success = (df["status"].str.upper() == "SUCCESS").sum()
    failed = (df["status"].str.upper() == "FAILED").sum()

    # Build histogram for durationSec
    dur = pd.to_numeric(df["durationSec"], errors="coerce").dropna().values
    if dur.size == 0:
        edges = [0, 5, 10, 20, 30]
        counts = [0, 0, 0, 0]
    else:
        # Choose fixed bins (seconds). Adjust if you want dynamic bins.
        edges = [0, 5, 10, 20, 30]
        counts, _ = np.histogram(dur, bins=edges)

        counts = counts.tolist()

    return {
        "status_pie": {"success": int(success), "failed": int(failed)},
        "time_hist": {"bin_edges": edges, "counts": counts},
    }


def load_files_for_date(day_iso: str) -> List[Dict[str, Any]]:
    """
    Returns rows for the Processed table:
      [{ filename, pages, started, ended, durationSec }]
    Prefers files_<date>.json; otherwise uses run_log.csv.
    """
    daily = _date_folder(day_iso)
    files_path = daily / f"files_{day_iso}.json"
    snap = _load_json(files_path, default=None)

    def _normalize_rows(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        out = []
        for r in rows:
            # Basic normalization and column mapping
            out.append({
                "filename": str(r.get("filename") or r.get("fileName") or ""),
                "pages": int(r.get("pages", r.get("pagesProcessed", 0)) or 0),
                "started": str(r.get("started") or r.get("startedAt") or ""),
                "ended": str(r.get("ended") or r.get("completedAt") or ""),
                "durationSec": float(_safe_float(r.get("durationSec", r.get("durationSeconds", 0.0)))),
            })
        # Filter PA only
        return [r for r in out if r["filename"].upper().startswith(f"{ACTIVE_PREFIX}_") or r["filename"].upper().startswith(f"{ACTIVE_PREFIX}-") or r["filename"].upper().startswith(f"{ACTIVE_PREFIX}")]

    if isinstance(snap, list) and snap:
        return _normalize_rows(snap)

    # Fallback: run_log.csv
    df = _read_run_log_filtered(day_iso)
    if df.empty:
        return []

    rows = []
    for _, row in df.iterrows():
        rows.append({
            "filename": str(row.get("filename", "")),
            "pages": int(row.get("pages", 0) if pd.notna(row.get("pages")) else 0),
            "started": str(row.get("started", "")),
            "ended": str(row.get("ended", "")),
            "durationSec": float(row.get("durationSec", np.nan)) if pd.notna(row.get("durationSec")) else 0.0,
        })
    return rows


def load_file_detail(day_iso: str, filename: str) -> Dict[str, Any]:
    """
    Returns:
      {
        "fields": {...},            # extracted entities
        "images": ["/static/conv/<img>.png", ...]   # preview image URLs
      }
    Looks for <date>/<basename>_output.json and images in CONV_DIR starting with <basename>_page_*.png
    """
    basename = Path(filename).stem  # strip .pdf
    daily = _date_folder(day_iso)

    # Fields JSON
    fields_json = daily / f"{basename}_output.json"
    fields = _load_json(fields_json, default={})
    if not isinstance(fields, dict):
        fields = {}

    # Images
    images = []
    if CONV_DIR.exists():
        # All files like <basename>_page_*.png
        for p in sorted(CONV_DIR.glob(f"{basename}_page_*.png")):
            # We expect the static server to serve /static/conv/ -> CONV_DIR
            images.append(f"/static/conv/{p.name}")

    return {"fields": fields, "images": images}
