import json
import os
import sys
import argparse
from datetime import datetime, date
from pathlib import Path

from colorama import init, Fore, Back, Style
init(autoreset=True)  # Required for Windows CMD

# ─── Storage ──────────────────────────────────────────────────────────────────

DATA_FILE = Path.home() / ".assigno_data.json"

def load_data():
    if DATA_FILE.exists():
        with open(DATA_FILE, "r") as f:
            return json.load(f)
    return {"assignments": []}

def save_data(data):
    with open(DATA_FILE, "w") as f:
        json.dump(data, f, indent=2)

def next_id(assignments):
    return max((a["id"] for a in assignments), default=0) + 1

# ─── Banner ───────────────────────────────────────────────────────────────────

def print_banner():
    print()
    print(Fore.CYAN + Style.BRIGHT + "    db    .d8888.  .d8888. d888888b  d888b  d8b   db  .d88b. ")
    print(Fore.CYAN + Style.BRIGHT + "   d88b   88'  YP 88'  YP   `88'   88' Y8b 888o  88 .8P  Y8.")
    print(Fore.CYAN + Style.BRIGHT + "  d8'`8b  `8bo.   `8bo.      88    88      88V8o 88 88    88 ")
    print(Fore.CYAN + Style.BRIGHT + " d8'  `8b   `Y8b.   `Y8b.    88    88  ooo 88 V8o88 88    88 ")
    print(Fore.CYAN + Style.BRIGHT + "d8'    `8b db   8D db   8D   .88.  88. ~8~ 88  V888 `8b  d8' ")
    print(Fore.CYAN + Style.BRIGHT + "YP      YP `8888Y' `8888Y' Y888888P  Y888P  VP   V8P  `Y88P' ")
    print(Style.DIM + "    your no-nonsense assignment tracker  --  v1.0.0")
    print()

# ─── Helpers ──────────────────────────────────────────────────────────────────

def parse_date(date_str, prompt_on_fail=False):
    """
    Strictly accepts DD/MM/YY format only.
    - Validates structure with regex before parsing
    - Rejects impossible dates (e.g. 32/01/25, 29/02/25 on non-leap year)
    - Rejects past dates
    - If prompt_on_fail=True, keeps asking interactively instead of exiting
    """
    import re

    def _try_parse(s):
        s = s.strip()

        # Must match exactly DD/MM/YY
        if not re.fullmatch(r"\d{2}/\d{2}/\d{2}", s):
            return None, "  [!] Format must be DD/MM/YY  (e.g. 25/03/26)"

        try:
            parsed = datetime.strptime(s, "%d/%m/%y")
        except ValueError:
            # strptime failed — impossible date like 30/02/26
            parts = s.split("/")
            return None, f"  [!] '{s}' is not a real date. Check day/month values."

        # Reject past dates
        if parsed.date() < date.today():
            return None, f"  [!] '{s}' is in the past. Enter a future date."

        return parsed.strftime("%Y-%m-%d"), None

    result, error = _try_parse(date_str)
    if result:
        return result

    if not prompt_on_fail:
        print(Fore.RED + error)
        sys.exit(1)

    # Interactive retry loop
    print(Fore.RED + error)
    while True:
        date_str = input(Fore.CYAN + "  |  Due date : " + Style.RESET_ALL)
        result, error = _try_parse(date_str)
        if result:
            return result
        print(Fore.RED + error)

def days_until(due_str):
    today = date.today()
    due = datetime.strptime(due_str, "%Y-%m-%d").date()
    return (due - today).days

def due_label(due_str):
    diff = days_until(due_str)
    due_fmt = datetime.strptime(due_str, "%Y-%m-%d").strftime("%d %b %Y")
    if diff < 0:
        return Fore.RED + f"{due_fmt} (overdue by {abs(diff)} day(s))"
    elif diff == 0:
        return Fore.YELLOW + Style.BRIGHT + f"{due_fmt} (DUE TODAY!)"
    elif diff <= 3:
        return Fore.YELLOW + f"{due_fmt} (in {diff} day(s))"
    else:
        return Fore.GREEN + f"{due_fmt} (in {diff} day(s))"

def status_badge(done):
    if done:
        return Fore.GREEN + "[DONE]   "
    return Fore.MAGENTA + "[PENDING]"

def print_assignment(a, show_class=True):
    id_str = Style.DIM + f"#{a['id']}"
    badge  = status_badge(a["done"])
    title  = (Style.DIM if a["done"] else Style.BRIGHT) + a["title"]
    due    = due_label(a["due"]) if not a["done"] else Style.DIM + datetime.strptime(a["due"], "%Y-%m-%d").strftime("%d %b %Y")

    print(f"  {id_str}  {badge}  {title}")
    if show_class:
        print(f"        " + Fore.CYAN + f"Class : {a['class']}")
    print(f"        " + Style.DIM + "Due   : " + Style.RESET_ALL + due)
    if a.get("notes"):
        print(f"        " + Style.DIM + f"Notes : {a['notes']}")
    print()

def divider(char="-", width=58):
    print(Style.DIM + char * width)

# ─── Commands ─────────────────────────────────────────────────────────────────

def cmd_add(args):
    data = load_data()
    aid = next_id(data["assignments"])

    print(Fore.CYAN + Style.BRIGHT + "  +-- Add New Assignment " + "-" * 34)

    title = args.title or input(Fore.CYAN + "  |  Title    : " + Style.RESET_ALL)
    cls   = (args.cls   or input(Fore.CYAN + "  |  Class    : " + Style.RESET_ALL)).upper().strip()
    if args.due:
        due = parse_date(args.due, prompt_on_fail=False)
    else:
        raw_due = input(Fore.CYAN + "  |  Due date : " + Style.RESET_ALL)
        due = parse_date(raw_due, prompt_on_fail=True)
    notes = args.notes  or input(Fore.CYAN + "  |  Notes    : " + Style.RESET_ALL)

    assignment = {
        "id":    aid,
        "title": title.strip(),
        "class": cls,
        "due":   due,
        "notes": notes.strip(),
        "done":  False,
    }

    data["assignments"].append(assignment)
    save_data(data)

    print(Fore.CYAN + Style.BRIGHT + "  +" + "-" * 57)
    print(Fore.GREEN + f"\n  [+] Assignment #{aid} added successfully!\n")

def cmd_list(args):
    data = load_data()
    assignments = data["assignments"]

    if not assignments:
        print(Fore.YELLOW + "\n  No assignments yet. Run: python assigno.py add\n")
        return

    classes = {}
    for a in assignments:
        classes.setdefault(a["class"], []).append(a)

    if args.pending:
        for cls in classes:
            classes[cls] = [a for a in classes[cls] if not a["done"]]
        classes = {k: v for k, v in classes.items() if v}

    if not classes:
        print(Fore.GREEN + "\n  All assignments done! Great work.\n")
        return

    print()
    for cls_name in sorted(classes.keys()):
        items = sorted(classes[cls_name], key=lambda a: a["due"])
        count = len(items)
        print(Back.BLUE + Fore.WHITE + Style.BRIGHT + f"  {cls_name}  " + Style.RESET_ALL +
              Style.DIM + f"  ({count} assignment{'s' if count != 1 else ''})")
        divider()
        for a in items:
            print_assignment(a, show_class=False)

def cmd_filter(args):
    data = load_data()
    target = parse_date(args.date)
    matched = [a for a in data["assignments"] if a["due"] == target]

    date_fmt = datetime.strptime(target, "%Y-%m-%d").strftime("%d %B %Y")
    print(Fore.CYAN + Style.BRIGHT + f"\n  Assignments due {date_fmt}:\n")

    if not matched:
        print(Style.DIM + "  None found for that date.\n")
        return

    for a in matched:
        print_assignment(a)

def cmd_done(args):
    data = load_data()
    aid = args.id

    for a in data["assignments"]:
        if a["id"] == aid:
            if a["done"]:
                print(Fore.YELLOW + f"\n  Assignment #{aid} is already marked done.\n")
                return
            a["done"] = True
            save_data(data)
            print(Fore.GREEN + f"\n  [+] '{a['title']}' marked as done! Nice work.\n")
            return

    print(Fore.RED + f"\n  [!] No assignment with ID #{aid} found.\n")

def cmd_delete(args):
    data = load_data()
    aid = args.id
    before = len(data["assignments"])
    data["assignments"] = [a for a in data["assignments"] if a["id"] != aid]

    if len(data["assignments"]) == before:
        print(Fore.RED + f"\n  [!] No assignment with ID #{aid} found.\n")
        return

    save_data(data)
    print(Fore.GREEN + f"\n  [+] Assignment #{aid} deleted.\n")

def cmd_summary(args):
    data = load_data()
    assignments = data["assignments"]
    total   = len(assignments)
    done    = sum(1 for a in assignments if a["done"])
    pending = total - done
    overdue = sum(1 for a in assignments if not a["done"] and days_until(a["due"]) < 0)

    print(Fore.CYAN + Style.BRIGHT + "  +-- Summary " + "-" * 45)
    print(f"  |  " + Style.BRIGHT + f"Total    : {total}")
    print(f"  |  " + Fore.GREEN   + f"Done     : {done}")
    print(f"  |  " + Fore.MAGENTA + f"Pending  : {pending}")
    if overdue:
        print(f"  |  " + Fore.RED + Style.BRIGHT + f"Overdue  : {overdue}")
    print(Fore.CYAN + Style.BRIGHT + "  +" + "-" * 57)
    print()

# ─── Entry Point ──────────────────────────────────────────────────────────────

def main():
    print_banner()

    parser = argparse.ArgumentParser(
        prog="assigno",
        description="A CLI assignment manager for students",
        formatter_class=argparse.RawTextHelpFormatter
    )
    sub = parser.add_subparsers(dest="command", metavar="command")

    p_add = sub.add_parser("add",    help="Add a new assignment")
    p_add.add_argument("--title",    help="Assignment title")
    p_add.add_argument("--cls",      help="Class name (e.g. INTRO TO AI)")
    p_add.add_argument("--due",      help="Due date in DD/MM/YY format (e.g. 25/03/26)")
    p_add.add_argument("--notes",    help="Optional notes")

    p_list = sub.add_parser("list",  help="List all assignments grouped by class")
    p_list.add_argument("--pending", action="store_true", help="Show only pending assignments")

    p_filter = sub.add_parser("filter", help="Filter assignments by due date")
    p_filter.add_argument("date",       help="Date in DD/MM/YY format (e.g. 25/03/26)")

    p_done = sub.add_parser("done",  help="Mark an assignment as done")
    p_done.add_argument("id", type=int, help="Assignment ID")

    p_del = sub.add_parser("delete", help="Delete an assignment")
    p_del.add_argument("id", type=int, help="Assignment ID")

    sub.add_parser("summary",        help="Show a stats summary")

    args = parser.parse_args()

    if   args.command == "add":     cmd_add(args)
    elif args.command == "list":    cmd_list(args)
    elif args.command == "filter":  cmd_filter(args)
    elif args.command == "done":    cmd_done(args)
    elif args.command == "delete":  cmd_delete(args)
    elif args.command == "summary": cmd_summary(args)
    else:
        parser.print_help()
        print()

if __name__ == "__main__":
    main()
