#!/usr/bin/env python3
import argparse
import re
from datetime import date, datetime, timedelta
from pathlib import Path


H1_RE = re.compile(r"^# (?!#)")
CYCLE_RE = re.compile(
    r"^# Cycle (\d+) \((\d{2}/\d{2}/\d{4}) - (\d{2}/\d{2}/\d{4})\)$"
)


def previous_sun_sat(today: date):
    # End on the most recent Saturday.
    days_since_saturday = (today.weekday() + 2) % 7
    end = today - timedelta(days=days_since_saturday)
    start = end - timedelta(days=6)
    return start, end


def fmt(d: date):
    return d.strftime("%m/%d/%Y")


def parse_date(s: str):
    return datetime.strptime(s, "%m/%d/%Y").date()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "file",
        nargs="?",
        default="~/.local/share/os/tasks.md",
        help="Task markdown file (default: ~/.local/share/os/tasks.md)",
    )
    parser.add_argument("--today", help="Override today, format YYYY-MM-DD")
    args = parser.parse_args()

    path = Path(args.file).expanduser()
    lines = path.read_text().splitlines(keepends=True)

    today = (
        datetime.strptime(args.today, "%Y-%m-%d").date()
        if args.today
        else date.today()
    )

    target_start, target_end = previous_sun_sat(today)

    cycles = []
    for line in lines:
        m = CYCLE_RE.match(line.strip())
        if m:
            cycles.append((int(m.group(1)), parse_date(m.group(2)), parse_date(m.group(3))))

    max_cycle = max((cycle[0] for cycle in cycles), default=0)
    latest_end = max((cycle[2] for cycle in cycles), default=None)

    if latest_end is not None and latest_end >= target_end:
        print(f"Nothing to do; latest cycle already ends {fmt(latest_end)}")
        return

    missing_cycles = []
    if latest_end is None:
        missing_cycles.append((max_cycle + 1, target_start, target_end))
    else:
        cycle_start = latest_end + timedelta(days=1)
        cycle_num = max_cycle + 1
        while cycle_start + timedelta(days=6) <= target_end:
            cycle_end = cycle_start + timedelta(days=6)
            missing_cycles.append((cycle_num, cycle_start, cycle_end))
            cycle_start += timedelta(days=7)
            cycle_num += 1

    if not missing_cycles:
        print("Nothing to do")
        return

    current_start = None
    for i, line in enumerate(lines):
        if line.strip() == "# Current":
            current_start = i
            break

    if current_start is None:
        raise SystemExit("Could not find '# Current' section")

    current_end = len(lines)
    for i in range(current_start + 1, len(lines)):
        if H1_RE.match(lines[i]):
            current_end = i
            break

    done_header = None
    for i in range(current_start + 1, current_end):
        if lines[i].strip() == "## Done":
            done_header = i
            break

    if done_header is None:
        raise SystemExit("Could not find '## Done' under '# Current'")

    done_start = done_header + 1
    done_end = current_end
    for i in range(done_start, current_end):
        if lines[i].startswith("## "):
            done_end = i
            break

    done_lines = lines[done_start:done_end]

    while done_lines and not done_lines[0].strip():
        done_lines.pop(0)
    while done_lines and not done_lines[-1].strip():
        done_lines.pop()

    # Empty Current -> Done section. If there are no done items, still create
    # the new cycle block with an empty Done section.
    lines = lines[:done_start] + ["\n"] + lines[done_end:]

    # Re-find end of Current after edit.
    current_start = next(i for i, line in enumerate(lines) if line.strip() == "# Current")
    insert_at = len(lines)
    for i in range(current_start + 1, len(lines)):
        if H1_RE.match(lines[i]):
            insert_at = i
            break

    cycle_block = []
    latest_cycle = missing_cycles[-1]
    for cycle_num, cycle_start, cycle_end in reversed(missing_cycles):
        cycle_block.extend([
            f"# Cycle {cycle_num} ({fmt(cycle_start)} - {fmt(cycle_end)})\n",
            "\n",
        ])
        if (cycle_num, cycle_start, cycle_end) == latest_cycle:
            cycle_block.extend(done_lines)
        cycle_block.append("\n")

    prefix = lines[:insert_at]
    suffix = lines[insert_at:]

    if prefix and prefix[-1].strip():
        prefix.append("\n")

    if suffix and suffix[0].strip():
        cycle_block.append("\n")

    path.write_text("".join(prefix + cycle_block + suffix))

    if len(missing_cycles) == 1:
        cycle_num, cycle_start, cycle_end = missing_cycles[0]
        print(f"Created # Cycle {cycle_num} ({fmt(cycle_start)} - {fmt(cycle_end)})")
    else:
        first_num = missing_cycles[0][0]
        last_num = missing_cycles[-1][0]
        print(f"Created cycles {first_num}-{last_num}")


if __name__ == "__main__":
    main()
