#!/usr/bin/env python3
"""agenticstats — Weekly-style developer activity report."""

import argparse
import json
import re
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path

DEV_DIR = Path.home() / "dev"
CLAUDE_DIR = Path.home() / ".claude" / "projects" / "-home-krisyotam"
USER = "Kris Yotam"


# ── time ranges ──────────────────────────────────────────────────────

def get_range(mode):
    """Return (start_dt, end_dt, label) for the given mode."""
    now = datetime.now(timezone.utc)
    today = now.replace(hour=0, minute=0, second=0, microsecond=0)

    if mode == "d":
        start = today
        label = now.strftime("%b %d, %Y")
        return start, now, f"Today — {label}"
    elif mode == "w":
        weekday = today.weekday()
        start = today - timedelta(days=weekday)
        end_day = start + timedelta(days=6)
        if start.month == end_day.month:
            label = f"{start.strftime('%b %d')}–{end_day.strftime('%d, %Y')}"
        else:
            label = f"{start.strftime('%b %d')} – {end_day.strftime('%b %d, %Y')}"
        return start, now, f"Week of {start.strftime('%b %d')}"
    elif mode == "m":
        start = today.replace(day=1)
        label = now.strftime("%B %Y")
        return start, now, label
    elif mode == "y":
        start = today.replace(month=1, day=1)
        label = now.strftime("%Y")
        return start, now, label
    elif mode == "a":
        start = datetime(2020, 1, 1, tzinfo=timezone.utc)
        return start, now, "All Time"


# ── git helpers ──────────────────────────────────────────────────────

def ts_fmt(dt):
    return dt.strftime("%Y-%m-%dT%H:%M:%SZ")


def find_repos(dev_dir):
    repos = []
    if not dev_dir.is_dir():
        return repos
    for d in sorted(dev_dir.iterdir()):
        if d.is_dir() and (d / ".git").exists():
            repos.append(d)
    return repos


def git_stats(repo, since, until):
    """Return (commits, insertions, deletions, is_solo) for a repo in range."""
    since_s, until_s = ts_fmt(since), ts_fmt(until)

    try:
        r = subprocess.run(
            ["git", "-C", str(repo), "log",
             f"--since={since_s}", f"--until={until_s}", "--oneline"],
            capture_output=True, text=True, timeout=10)
        commits = len(r.stdout.strip().splitlines()) if r.stdout.strip() else 0
    except Exception:
        return 0, 0, 0, True

    if commits == 0:
        return 0, 0, 0, True

    # LOC via numstat
    insertions = deletions = 0
    try:
        r = subprocess.run(
            ["git", "-C", str(repo), "log",
             f"--since={since_s}", f"--until={until_s}",
             "--pretty=tformat:", "--numstat"],
            capture_output=True, text=True, timeout=30)
        for line in r.stdout.strip().splitlines():
            parts = line.split()
            if len(parts) >= 2:
                try: insertions += int(parts[0])
                except ValueError: pass
                try: deletions += int(parts[1])
                except ValueError: pass
    except Exception:
        pass

    # Solo check
    try:
        r = subprocess.run(
            ["git", "-C", str(repo), "log",
             f"--since={since_s}", f"--until={until_s}", "--format=%aN"],
            capture_output=True, text=True, timeout=10)
        is_solo = len(set(r.stdout.strip().splitlines())) <= 1
    except Exception:
        is_solo = True

    return commits, insertions, deletions, is_solo


# ── AI session stats ─────────────────────────────────────────────────

def count_ai_sessions(claude_dir, since, until):
    cc = gpt = 0
    if not claude_dir.is_dir():
        return cc, gpt

    for f in claude_dir.glob("*.jsonl"):
        try:
            with open(f) as fh:
                first = fh.readline().strip()
                if not first:
                    continue
                data = json.loads(first)
                ts_str = data.get("timestamp")
                if not ts_str:
                    continue
                ts = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
                if since <= ts <= until:
                    if "[ChatGPT Import]" in data.get("summary", ""):
                        gpt += 1
                    else:
                        cc += 1
        except Exception:
            continue

    return cc, gpt


# ── per-commit stats for ship + top work ─────────────────────────────

def get_commit_stats(repos, since, until):
    """Return list of (insertions+deletions, files, msg, repo_name) per commit."""
    since_s, until_s = ts_fmt(since), ts_fmt(until)
    results = []

    for repo in repos:
        try:
            # Get each commit hash + message
            r = subprocess.run(
                ["git", "-C", str(repo), "log",
                 f"--since={since_s}", f"--until={until_s}",
                 "--format=%H\t%s"],
                capture_output=True, text=True, timeout=15)
            if not r.stdout.strip():
                continue

            for line in r.stdout.strip().splitlines():
                parts = line.split("\t", 1)
                if len(parts) < 2:
                    continue
                sha, msg = parts
                if msg.startswith("Merge"):
                    continue

                # Get numstat for this specific commit
                r2 = subprocess.run(
                    ["git", "-C", str(repo), "diff", "--numstat",
                     f"{sha}~1", sha],
                    capture_output=True, text=True, timeout=10)

                ins = dels = files = 0
                for sl in r2.stdout.strip().splitlines():
                    sp = sl.split()
                    if len(sp) >= 3:
                        files += 1
                        try: ins += int(sp[0])
                        except ValueError: pass
                        try: dels += int(sp[1])
                        except ValueError: pass

                results.append((ins + dels, files, ins, dels, msg, repo.name))
        except Exception:
            continue

    results.sort(key=lambda x: x[0], reverse=True)
    return results


# ── streak calculation ───────────────────────────────────────────────

def get_shipping_streak(repos):
    today = datetime.now(timezone.utc).date()
    day = today
    streak = 0

    while True:
        day_start = datetime(day.year, day.month, day.day, tzinfo=timezone.utc)
        day_end = day_start + timedelta(days=1)
        has_commit = False

        for repo in repos:
            try:
                r = subprocess.run(
                    ["git", "-C", str(repo), "log",
                     f"--since={ts_fmt(day_start)}", f"--until={ts_fmt(day_end)}",
                     "--oneline", "-1"],
                    capture_output=True, text=True, timeout=5)
                if r.stdout.strip():
                    has_commit = True
                    break
            except Exception:
                continue

        if has_commit:
            streak += 1
            day -= timedelta(days=1)
        else:
            break

    return streak


# ── formatting ───────────────────────────────────────────────────────

def fmt_loc(n):
    abs_n = abs(n)
    if abs_n >= 1_000_000:
        s = f"{abs_n/1_000_000:.1f}M"
    elif abs_n >= 1_000:
        s = f"{abs_n/1_000:.1f}k"
    else:
        s = str(abs_n)
    return s


def fmt_net(n):
    sign = "+" if n >= 0 else "-"
    return f"{sign}{fmt_loc(n)}"


GREY = "\033[38;5;240m"
WHITE = "\033[97m"
DIM = "\033[2m"
BOLD = "\033[1m"
RESET = "\033[0m"
FIRE = "\U0001f525"

_ansi_re = re.compile(r'\033\[[0-9;]*m')

def strip_ansi(s):
    return _ansi_re.sub('', s)

def box_line(text, width=78):
    vis_len = len(strip_ansi(text))
    padding = max(0, width - vis_len - 4)
    return f"  {GREY}\u2502{RESET} {text}{' ' * padding} {GREY}\u2502{RESET}"

def box_top(width=78):
    return f"  {GREY}\u250c{'\u2500' * (width - 2)}\u2510{RESET}"

def box_bottom(width=78):
    return f"  {GREY}\u2514{'\u2500' * (width - 2)}\u2518{RESET}"

def box_sep(width=78):
    return f"  {GREY}\u251c{'\u2500' * (width - 2)}\u2524{RESET}"


# ── rendering ────────────────────────────────────────────────────────

def render(mode, since, until, label):
    repos = find_repos(DEV_DIR)

    # Per-repo stats
    project_stats = []
    total_commits = total_ins = total_del = 0

    for repo in repos:
        commits, ins, dels, solo = git_stats(repo, since, until)
        if commits > 0:
            project_stats.append((repo.name, commits, ins, dels, solo))
            total_commits += commits
            total_ins += ins
            total_del += dels

    project_stats.sort(key=lambda x: x[1], reverse=True)
    active = len(project_stats)

    # AI sessions
    cc_sessions, gpt_sessions = count_ai_sessions(CLAUDE_DIR, since, until)
    total_sessions = cc_sessions + gpt_sessions

    # Streak
    streak = get_shipping_streak(repos)

    # Per-commit stats for ship + top work
    commit_stats = get_commit_stats(repos, since, until)

    # ── output ──
    period_map = {"d": "Day", "w": "Week", "m": "Month", "y": "Year", "a": "All Time"}
    ship_map = {"d": "SHIP OF THE DAY", "w": "SHIP OF THE WEEK", "m": "SHIP OF THE MONTH", "y": "SHIP OF THE YEAR", "a": "BIGGEST SHIP"}

    print()
    print(f"  {BOLD}{WHITE}\u2728 Your {period_map[mode]}: {USER} \u2014 {label}{RESET}")
    print()
    print(box_top())
    print(box_line(f"{BOLD}{WHITE}{USER.upper()} \u2014 {label}{RESET}"))
    print(box_sep())
    print(box_line(""))

    net = total_ins - total_del
    print(box_line(f"  {total_commits} commits across {active} projects"))
    print(box_line(f"  +{fmt_loc(total_ins)} LOC added \u00b7 {fmt_loc(total_del)} LOC deleted \u00b7 {fmt_net(net)} net"))

    if total_sessions > 0:
        parts = []
        if cc_sessions > 0:
            parts.append(f"CC: {cc_sessions}")
        if gpt_sessions > 0:
            parts.append(f"GPT: {gpt_sessions}")
        print(box_line(f"  {total_sessions} AI coding sessions ({', '.join(parts)})"))

    if streak > 0:
        print(box_line(f"  {streak}-day shipping streak {FIRE}"))

    print(box_line(""))
    print(box_sep())
    print(box_line(f"  {BOLD}PROJECTS{RESET}"))
    print(box_line(""))

    if project_stats:
        name_w = max(max(len(p[0]) for p in project_stats[:15]), 12)
        for name, commits, ins, dels, solo in project_stats[:15]:
            tag = "solo" if solo else "team"
            net_loc = ins - dels
            line = f"  {name:<{name_w}}  {commits:>4} commits   {fmt_net(net_loc):>9} LOC   {tag}"
            print(box_line(line))
    else:
        print(box_line("  No commits in this period."))

    print(box_line(""))
    print(box_sep())
    print(box_line(f"  {BOLD}{ship_map[mode]}{RESET}"))

    if commit_stats:
        total_loc, files, ins, dels, msg, repo_name = commit_stats[0]
        if len(msg) > 60:
            msg = msg[:57] + "..."
        print(box_line(f"  {msg}"))
        print(box_line(f"  \u2014 {fmt_loc(total_loc)} lines across {files} files ({repo_name})"))
    else:
        print(box_line("  Nothing shipped yet."))

    print(box_line(""))
    print(box_sep())
    print(box_line(f"  {BOLD}TOP WORK{RESET}"))

    if commit_stats:
        seen = set()
        count = 0
        for _, _, _, _, msg, rn in commit_stats:
            if msg in seen:
                continue
            seen.add(msg)
            if len(msg) > 66:
                msg = msg[:63] + "..."
            print(box_line(f"  \u2022 {msg}"))
            count += 1
            if count >= 5:
                break
    else:
        print(box_line("  No notable commits in this period."))

    print(box_line(""))
    print(box_sep())
    print(box_line(f"  {DIM}Powered by agenticstats{RESET}"))
    print(box_bottom())
    print()


# ── CLI ──────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(
        prog="agenticstats",
        description="Developer activity report."
    )
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-d", action="store_true", help="Daily (today)")
    group.add_argument("-w", action="store_true", help="Weekly (default)")
    group.add_argument("-m", action="store_true", help="Monthly")
    group.add_argument("-y", action="store_true", help="Yearly")
    group.add_argument("-a", action="store_true", help="All time")

    args = parser.parse_args()

    if args.d:     mode = "d"
    elif args.m:   mode = "m"
    elif args.y:   mode = "y"
    elif args.a:   mode = "a"
    else:          mode = "w"

    since, until, label = get_range(mode)
    render(mode, since, until, label)


if __name__ == "__main__":
    main()
