#!/usr/bin/env python
# CLI + renderers (daily/weekly/monthly) and scaffolding helpers

import argparse
import re
import sys
import shutil
from contextlib import ExitStack
from importlib.resources import files, as_file
from pathlib import Path
from typing import Iterable, List, Optional, Tuple
import datetime as dt

# Single source of truth for version
try:
    from . import __version__
except Exception:  # pragma: no cover
    __version__ = "0+unknown"


# -------------------------
# Utilities
# -------------------------

_DATE_RX = re.compile(r"notes-(\d{4}-\d{2}-\d{2})\.md$", re.I)


def _iter_log_files(logs_dir: Path) -> Iterable[Path]:
    if not logs_dir.exists():
        return []
    for p in sorted(logs_dir.glob("notes-*.md")):
        if _DATE_RX.search(p.name):
            yield p


def _parse_md_section(lines: List[str], header: str) -> List[str]:
    """Extract bullet lines from a '## Header' section."""
    out: List[str] = []
    in_section = False
    hdr_re = re.compile(rf"^##\s*{re.escape(header)}\s*$", re.I)
    for line in lines:
        if hdr_re.match(line.strip()):
            in_section = True
            continue
        if in_section and line.startswith("## "):
            break
        if in_section:
            out.append(line.rstrip("\n"))
    # bullets only
    return [
        ln for ln in out if ln.strip().startswith("-") or ln.strip().startswith("•")
    ]


def _group_actions_by_priority(
    bullets: Iterable[str],
) -> Tuple[List[str], List[str], List[str]]:
    high: List[str] = []
    med: List[str] = []
    low: List[str] = []
    for b in bullets:
        sb = b.strip()
        tag = sb.lower()
        if "[high]" in tag:
            high.append(sb)
        elif "[medium]" in tag:
            med.append(sb)
        elif "[low]" in tag:
            low.append(sb)
        else:
            # untagged -> medium by default
            med.append(sb)
    return high, med, low


def _footer() -> str:
    return (
        "\n---\n_Digest generated by team-digest (v%s) — https://pypi.org/project/team-digest/_\n"
        % (__version__,)
    )


def _read_md(path: Path) -> List[str]:
    return path.read_text(encoding="utf-8").splitlines()


# -------------------------
# Renderers (daily/weekly/monthly)
# -------------------------


def render_daily(log_path: Path, group_actions: bool = True) -> str:
    if not log_path.exists():
        return (
            f"# Team Digest ({log_path.stem.replace('notes-','')})\n\n_No log for {log_path.stem.replace('notes-','')} in {log_path.parent.name}_\n"
            + _footer()
        )

    lines = _read_md(log_path)
    date = (
        _DATE_RX.search(log_path.name).group(1)
        if _DATE_RX.search(log_path.name)
        else "Unknown"
    )
    summary = _parse_md_section(lines, "Summary")
    decisions = _parse_md_section(lines, "Decisions")
    actions = _parse_md_section(lines, "Actions")
    risks = _parse_md_section(lines, "Risks")
    deps = _parse_md_section(lines, "Dependencies")
    notes = _parse_md_section(lines, "Notes")

    out: List[str] = [f"# Team Digest ({date})", ""]
    out.append("## Summary")
    out.extend(summary or ["_No summary._", ""])
    out.append("")
    out.append("## Decisions")
    out.extend(decisions or ["_No decisions._", ""])
    out.append("")

    out.append("## Actions")
    if group_actions:
        hi, me, lo = _group_actions_by_priority(actions)
        if not (hi or me or lo):
            out.append("_No actions._")
        else:
            if hi:
                out.append("### High priority")
                out.extend(hi)
            if me:
                out.append("### Medium priority")
                out.extend(me)
            if lo:
                out.append("### Low priority")
                out.extend(lo)
    else:
        out.extend(actions or ["_No actions._"])
    out.append("")

    out.append("## Risks")
    out.extend(risks or ["_No risks._"])
    out.append("")
    out.append("## Dependencies")
    out.extend(deps or ["_No dependencies._"])
    out.append("")
    out.append("## Notes")
    out.extend(notes or ["_No notes._"])
    out.append("")
    out.append(_footer())
    return "\n".join(out)


def render_range(
    logs_dir: Path,
    start: dt.date,
    end: dt.date,
    title: str,
    group_actions: bool = True,
    emit_kpis: bool = False,
    owner_breakdown: bool = False,
) -> str:
    matched: List[Tuple[dt.date, Path]] = []
    for p in _iter_log_files(logs_dir):
        m = _DATE_RX.search(p.name)
        if not m:
            continue
        d = dt.date.fromisoformat(m.group(1))
        if start <= d <= end:
            matched.append((d, p))

    matched.sort(key=lambda t: t[0])
    actions_all: List[str] = []
    summaries: List[str] = []
    decisions_all: List[str] = []
    risks_all: List[str] = []
    deps_all: List[str] = []
    notes_all: List[str] = []

    for _, p in matched:
        lines = _read_md(p)
        summaries += _parse_md_section(lines, "Summary")
        decisions_all += _parse_md_section(lines, "Decisions")
        a = _parse_md_section(lines, "Actions")
        actions_all += a
        risks_all += _parse_md_section(lines, "Risks")
        deps_all += _parse_md_section(lines, "Dependencies")
        notes_all += _parse_md_section(lines, "Notes")

    out: List[str] = [
        f"# {title}",
        "",
        f"_Range: {start} → {end} | Source: {logs_dir.name} | Days matched: {len(matched)} | Actions: {len(actions_all)}_",
        "",
    ]
    if emit_kpis:
        hi, me, lo = _group_actions_by_priority(actions_all)
        owners = {}
        ow_re = re.compile(
            r"\]\s+([A-Za-z \.-]+)\s+to\s+", re.I
        )  # naive "Owner to do X"
        for b in actions_all:
            tag = b.lower()
            owner = None
            m = ow_re.search(b)
            if m:
                owner = m.group(1).strip()
            owner = owner or "Unassigned"
            bucket = "medium"
            if "[high]" in tag:
                bucket = "high"
            elif "[low]" in tag:
                bucket = "low"
            dct = owners.setdefault(owner, {"high": 0, "medium": 0, "low": 0})
            dct[bucket] += 1

        out.append("## Executive KPIs")
        out.append(
            f"- **Actions:** {len(actions_all)} (High: {len(hi)}, Medium: {len(me)}, Low: {len(lo)})"
        )
        out.append(
            f"- **Decisions:** {len(decisions_all)}   ·   **Risks:** {len(risks_all)}"
        )
        out.append(
            f"- **Owners:** {len(owners)}   ·   **Days with notes:** {len(matched)}"
        )
        out.append("")
        if owner_breakdown and owners:
            out.append("#### Owner breakdown (top)")
            out.append("| Owner | High | Medium | Low | Total |")
            out.append("|:------|----:|------:|---:|-----:|")
            for name, counts in sorted(
                owners.items(),
                key=lambda kv: (
                    -(kv[1]["high"]),
                    -(kv[1]["medium"]),
                    -(kv[1]["low"]),
                    kv[0].lower(),
                ),
            ):
                total = counts["high"] + counts["medium"] + counts["low"]
                out.append(
                    f"| {name} | {counts['high']} | {counts['medium']} | {counts['low']} | **{total}** |"
                )
            out.append("")

    # Sections
    out.append("## Summary")
    out.extend(summaries or ["_No summary._"])
    out.append("")
    out.append("## Decisions")
    out.extend(decisions_all or ["_No decisions._"])
    out.append("")
    out.append("## Actions")
    if group_actions:
        hi, me, lo = _group_actions_by_priority(actions_all)
        if not (hi or me or lo):
            out.append("_No actions._")
        else:
            if hi:
                out.append("### High priority")
                out.extend(hi)
            if me:
                out.append("### Medium priority")
                out.extend(me)
            if lo:
                out.append("### Low priority")
                out.extend(lo)
    else:
        out.extend(actions_all or ["_No actions._"])
    out.append("")
    out.append("## Risks")
    out.extend(risks_all or ["_No risks._"])
    out.append("")
    out.append("## Dependencies")
    out.extend(deps_all or ["_No dependencies._"])
    out.append("")
    out.append("## Notes")
    out.extend(notes_all or ["_No notes._"])
    out.append("")
    out.append(_footer())
    return "\n".join(out)


# -------------------------
# New: init + doctor
# -------------------------


def cmd_init(args: argparse.Namespace) -> int:
    target = Path(args.to).resolve()
    target.mkdir(parents=True, exist_ok=True)

    # locate packaged examples
    es = ExitStack()
    try:
        pkg_root = files("team_digest").joinpath("examples")
        examples_fs = es.enter_context(as_file(pkg_root))
        src = Path(examples_fs)
        dest = target
        if any(dest.iterdir()) and not args.force:
            print(
                f"[init] Destination '{dest}' is not empty. Use --force to overwrite.",
                file=sys.stderr,
            )
            return 2
        for sub in src.iterdir():
            dst = dest / sub.name
            if dst.exists() and args.force:
                if dst.is_file():
                    dst.unlink()
                else:
                    shutil.rmtree(dst)
            if sub.is_file():
                shutil.copy2(sub, dst)
            else:
                shutil.copytree(sub, dst, dirs_exist_ok=True)
        print(f"[init] Scaffolding complete → {dest}")
        print("[init] Try:")
        print(
            "  team-digest weekly --logs-dir logs --start 2025-10-13 --end 2025-10-19 --output outputs/weekly.md --group-actions --emit-kpis --owner-breakdown"
        )
        return 0
    finally:
        es.close()


def cmd_doctor(args: argparse.Namespace) -> int:
    logs_dir = Path(args.logs_dir).resolve()
    files_found = list(_iter_log_files(logs_dir))
    print(f"[doctor] logs_dir={logs_dir}")
    if not files_found:
        print(
            "[doctor] No matching 'notes-YYYY-MM-DD.md' files found.", file=sys.stderr
        )
        return 2
    dates = []
    for p in files_found:
        m = _DATE_RX.search(p.name)
        if m:
            dates.append(dt.date.fromisoformat(m.group(1)))
    dates.sort()
    print(f"[doctor] files={len(files_found)} first={dates[0]} last={dates[-1]}")
    return 0


# -------------------------
# CLI
# -------------------------


def main(argv: Optional[List[str]] = None) -> int:
    parser = argparse.ArgumentParser(
        prog="team-digest",
        description="Generate Daily/Weekly/Monthly digests from logs.",
    )
    parser.add_argument(
        "-V", "--version", action="version", version=f"team-digest {__version__}"
    )

    sub = parser.add_subparsers(dest="cmd", required=True)

    # daily
    p_d = sub.add_parser("daily", help="Build a daily digest for a single date")
    p_d.add_argument(
        "--logs-dir", required=True, help="Path to Markdown logs directory"
    )
    p_d.add_argument("--date", required=True, help="Date YYYY-MM-DD")
    p_d.add_argument("--output", "-o", required=True, help="Output file (.md or .json)")
    p_d.add_argument(
        "--group-actions", action="store_true", help="Group actions by priority/owner"
    )
    p_d.set_defaults(
        _fn=lambda a: print(
            render_daily(
                Path(a.logs_dir) / f"notes-{a.date}.md", group_actions=a.group_actions
            )
        )
    )

    # weekly
    p_w = sub.add_parser("weekly", help="Build a digest for a date range (inclusive)")
    p_w.add_argument("--logs-dir", required=True)
    p_w.add_argument("--start", required=True)
    p_w.add_argument("--end", required=True)
    p_w.add_argument("--output", "-o", required=True)
    p_w.add_argument("--group-actions", action="store_true")
    p_w.add_argument("--emit-kpis", action="store_true")
    p_w.add_argument("--owner-breakdown", action="store_true")
    p_w.set_defaults(
        _fn=lambda a: print(
            render_range(
                Path(a.logs_dir),
                dt.date.fromisoformat(a.start),
                dt.date.fromisoformat(a.end),
                f"Team Digest ({a.start} - {a.end})",
                group_actions=a.group_actions,
                emit_kpis=a.emit_kpis,
                owner_breakdown=a.owner_breakdown,
            )
        )
    )

    # monthly
    p_m = sub.add_parser(
        "monthly", help="Build a digest for a calendar month (or month-to-date)"
    )
    p_m.add_argument("--logs-dir", required=True)
    p_m.add_argument("--output", "-o", required=True)
    p_m.add_argument("--group-actions", action="store_true")
    p_m.add_argument("--emit-kpis", action="store_true")
    p_m.add_argument("--owner-breakdown", action="store_true")

    def _month_bounds(today: dt.date) -> Tuple[dt.date, dt.date]:
        start = today.replace(day=1)
        return start, today

    p_m.set_defaults(
        _fn=lambda a: print(
            render_range(
                Path(a.logs_dir),
                *_month_bounds(dt.date.today()),
                f"Team Digest ({dt.date.today().replace(day=1)} - {dt.date.today()})",
                group_actions=a.group_actions,
                emit_kpis=a.emit_kpis,
                owner_breakdown=a.owner_breakdown,
            )
        )
    )

    # NEW: init
    p_i = sub.add_parser("init", help="Scaffold example logs/configs into a folder")
    p_i.add_argument(
        "--to", default=".", help="Destination folder (default: current dir)"
    )
    p_i.add_argument("--force", action="store_true", help="Overwrite existing files")
    p_i.set_defaults(_fn=cmd_init)

    # NEW: doctor
    p_x = sub.add_parser("doctor", help="Validate logs folder and show a quick summary")
    p_x.add_argument(
        "--logs-dir", default="logs", help="Logs directory (default: logs)"
    )
    p_x.set_defaults(_fn=cmd_doctor)

    args = parser.parse_args(argv)

    # Dispatch
    buf = args._fn(args)
    # Renderers return string (print already handled in lambdas above); commands return exit code
    if isinstance(buf, str):
        out = Path(getattr(args, "output", "-"))
        if str(out) == "-" or not out:
            sys.stdout.write(buf)
        else:
            Path(out).parent.mkdir(parents=True, exist_ok=True)
            out.write_text(buf, encoding="utf-8")
    elif isinstance(buf, int):
        return buf

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
