# project_manager.py
"""
Note(Gemini):
Handles loading/saving of project .toml configurations.
Also handles serializing the discussion history into the TOML format using a special
@timestamp prefix to preserve the exact sequence of events.
"""
import subprocess
import datetime
import tomllib
import tomli_w
import re
from pathlib import Path

TS_FMT = "%Y-%m-%dT%H:%M:%S"


def now_ts() -> str:
    return datetime.datetime.now().strftime(TS_FMT)


def parse_ts(s: str):
    try:
        return datetime.datetime.strptime(s, TS_FMT)
    except Exception:
        return None


# ── entry serialisation ──────────────────────────────────────────────────────

def entry_to_str(entry: dict) -> str:
    """Serialise a disc entry dict -> stored string."""
    ts      = entry.get("ts", "")
    role    = entry.get("role", "User")
    content = entry.get("content", "")
    if ts:
        return f"@{ts}\n{role}:\n{content}"
    return f"{role}:\n{content}"


def str_to_entry(raw: str, roles: list[str]) -> dict:
    """Parse a stored string back to a disc entry dict."""
    ts   = ""
    rest = raw
    if rest.startswith("@"):
        nl = rest.find("\n")
        if nl != -1:
            ts   = rest[1:nl]
            rest = rest[nl + 1:]
    known    = roles or ["User", "AI", "Vendor API", "System"]
    role_pat = re.compile(
        r"^(?:\[)?(" + "|".join(re.escape(r) for r in known) + r")(?:\])?:?\s*$",
        re.IGNORECASE,
    )
    parts        = rest.split("\n", 1)
    matched_role = "User"
    content      = rest.strip()
    if parts:
        m = role_pat.match(parts[0].strip())
        if m:
            raw_role     = m.group(1)
            matched_role = next((r for r in known if r.lower() == raw_role.lower()), raw_role)
            content      = parts[1].strip() if len(parts) > 1 else ""
    return {"role": matched_role, "content": content, "collapsed": False, "ts": ts}


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

def get_git_commit(git_dir: str) -> str:
    try:
        r = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            capture_output=True, text=True, cwd=git_dir, timeout=5,
        )
        return r.stdout.strip() if r.returncode == 0 else ""
    except Exception:
        return ""


def get_git_log(git_dir: str, n: int = 5) -> str:
    try:
        r = subprocess.run(
            ["git", "log", "--oneline", f"-{n}"],
            capture_output=True, text=True, cwd=git_dir, timeout=5,
        )
        return r.stdout.strip() if r.returncode == 0 else ""
    except Exception:
        return ""


# ── default structures ───────────────────────────────────────────────────────

def default_discussion() -> dict:
    return {"git_commit": "", "last_updated": now_ts(), "history": []}


def default_project(name: str = "unnamed") -> dict:
    return {
        "project":     {"name": name, "git_dir": "", "system_prompt": "", "main_context": ""},
        "output":      {"output_dir": "./md_gen"},
        "files":       {"base_dir": ".", "paths": []},
        "screenshots": {"base_dir": ".", "paths": []},
        "gemini_cli":  {"binary_path": "gemini"},
        "deepseek":    {"reasoning_effort": "medium"},
        "agent":       {
            "tools": {
                "run_powershell": True,
                "read_file": True,
                "list_directory": True,
                "search_files": True,
                "get_file_summary": True,
                "web_search": True,
                "fetch_url": True
            }
        },
        "discussion":  {
            "roles":       ["User", "AI", "Vendor API", "System", "Reasoning"],
            "active":      "main",
            "discussions": {"main": default_discussion()},
        },
    }


# ── load / save ──────────────────────────────────────────────────────────────

def get_history_path(project_path: str | Path) -> Path:
    """Return the Path to the sibling history TOML file for a given project."""
    p = Path(project_path)
    return p.parent / f"{p.stem}_history.toml"


def load_project(path: str | Path) -> dict:
    """
    Load a project TOML file.
    Automatically migrates legacy 'discussion' keys to a sibling history file.
    """
    with open(path, "rb") as f:
        proj = tomllib.load(f)
    
    # Automatic Migration: move legacy 'discussion' to sibling file
    hist_path = get_history_path(path)
    if "discussion" in proj:
        disc = proj.pop("discussion")
        # Save to history file if it doesn't exist yet (or overwrite to migrate)
        with open(hist_path, "wb") as f:
            tomli_w.dump(disc, f)
        # Save the stripped project file
        save_project(proj, path)
        # Restore for the returned dict so GUI works as before
        proj["discussion"] = disc
    else:
        # Load from sibling if it exists
        if hist_path.exists():
            proj["discussion"] = load_history(path)
    
    return proj


def load_history(project_path: str | Path) -> dict:
    """Load the segregated discussion history from its dedicated TOML file."""
    hist_path = get_history_path(project_path)
    if hist_path.exists():
        with open(hist_path, "rb") as f:
            return tomllib.load(f)
    return {}


def save_project(proj: dict, path: str | Path, disc_data: dict | None = None):
    """
    Save the project TOML.
    If 'discussion' is present in proj, it is moved to the sibling history file.
    """
    # Ensure 'discussion' is NOT in the main project dict
    if "discussion" in proj:
        # If disc_data wasn't provided, use the one from proj
        if disc_data is None:
            disc_data = proj["discussion"]
        # Remove it so it doesn't get saved to the main file
        proj = dict(proj) # shallow copy to avoid mutating caller's dict
        del proj["discussion"]

    with open(path, "wb") as f:
        tomli_w.dump(proj, f)
        
    if disc_data:
        hist_path = get_history_path(path)
        with open(hist_path, "wb") as f:
            tomli_w.dump(disc_data, f)


# ── migration helper ─────────────────────────────────────────────────────────

def migrate_from_legacy_config(cfg: dict) -> dict:
    """Build a fresh project dict from a legacy flat config.toml. Does NOT save."""
    name = cfg.get("output", {}).get("namespace", "project")
    proj = default_project(name)
    for key in ("output", "files", "screenshots"):
        if key in cfg:
            proj[key] = dict(cfg[key])
    disc = cfg.get("discussion", {})
    proj["discussion"]["roles"] = disc.get("roles", ["User", "AI", "Vendor API", "System"])
    main_disc = proj["discussion"]["discussions"]["main"]
    main_disc["history"]      = disc.get("history", [])
    main_disc["last_updated"] = now_ts()
    return proj


# ── flat config for aggregate.run() ─────────────────────────────────────────

def flat_config(proj: dict, disc_name: str | None = None) -> dict:
    """Return a flat config dict compatible with aggregate.run()."""
    disc_sec  = proj.get("discussion", {})
    name      = disc_name or disc_sec.get("active", "main")
    disc_data = disc_sec.get("discussions", {}).get(name, {})
    return {
        "project":     proj.get("project", {}),
        "output":      proj.get("output", {}),
        "files":       proj.get("files",  {}),
        "screenshots": proj.get("screenshots", {}),
        "discussion":  {
            "roles":   disc_sec.get("roles", []),
            "history": disc_data.get("history", []),
        },
    }
