#!/usr/bin/env python3
import tomllib
import shutil
import tarfile
import tempfile
from datetime import datetime
from pathlib import Path
from enum import Enum


CONFIG_FILE = Path(__file__).parent / "config.toml"

def create_tool_bundle(bundle_name, entries):
    """
    Create a tar.gz bundle for a single tool.
    `entries` should be a list of entries for that tool only.
    """
    tempdir = Path(tempfile.mkdtemp(prefix="dotfile_manager"))

    bundle_dir = Path(f"{tempdir}/dotfiles-{bundle_name}")
    bundle_dir.mkdir()

    # Create extract.sh
    extract_script = bundle_dir / "extract.sh"
    with extract_script.open("w") as f:
        f.write("#!/bin/sh\n")
        f.write("set -e\n\n")
        f.write("# Extract dotfiles for {}\n".format(bundle_name))

        for entry in entries:
            src = Path(entry["repo_path"]).name
            dest = entry["system_path"].replace("$HOME", str(Path.home()))
            backup = f"{dest}.old"

            f.write(f"echo 'Installing {entry['repo_path']} -> {dest}'\n")
            f.write(f"if [ -e '{dest}' ]; then\n")
            f.write(f"  mv '{dest}' '{backup}'\n")
            f.write("fi\n")
            if entry["type"] == "dir":
                f.write(f"cp -r '{src}' '{dest}'\n")
            else:
                f.write(f"mkdir -p $(dirname '{dest}')\n")
                f.write(f"cp '{src}' '{dest}'\n")
            f.write("\n")

    extract_script.chmod(0o755)

    # Copy files into bundle_dir
    for entry in entries:
        src_path = Path(entry["repo_path"])
        dest_path = bundle_dir / src_path.name
        if src_path.is_dir():
            shutil.copytree(src_path, dest_path)
        else:
            dest_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(src_path, dest_path)

    # Create tar.gz
    tar_path = Path(f"dotfiles-{bundle_name}.tar.gz")
    with tarfile.open(tar_path, "w:gz") as tar:
        tar.add(bundle_dir, arcname=bundle_dir.name)

    # Cleanup temp directory
    shutil.rmtree(bundle_dir)

    print(f"Bundle created: {tar_path}")


class ToolType(str, Enum):
    FILE = "file"
    DIR = "dir"


def parse_dotfiles_toml(path: Path):
    """
    Parse a dotfiles TOML and return a flat list of entries.
    Rules:
      - Every entry (top-level or nested) must have a 'type' (file or dir)
      - Nested entries belong to the same tool
    Returns a list of dicts with keys: tool, repo_path, system_path, type, ...
    """
    with open(path, "rb") as f:
        cfg = tomllib.load(f)

    all_entries = []
    tools = cfg.get("tools", {})

    def process_table(tool_name, table):
        """
        Process a tool table: flatten nested entries, enforce 'type'.
        """
        # If table itself has 'type', treat it as a single entry
        if "type" in table:
            if table["type"] not in ("file", "dir"):
                raise ValueError(f"Invalid type for tool '{tool_name}': {table['type']}")
            entry = dict(table)
            entry["tool"] = tool_name
            all_entries.append(entry)
            return  # stop recursion here

        # Otherwise recurse into nested tables
        for key, value in table.items():
            if isinstance(value, dict):
                process_table(tool_name, value)
            else:
                raise ValueError(f"Invalid entry in tool '{tool_name}': {key}={value}")

    for tool_name, tool_data in tools.items():
        if not isinstance(tool_data, dict):
            raise ValueError(f"Tool '{tool_name}' must be a table")
        process_table(tool_name, tool_data)

    return all_entries


def filter_entries(entries, targets=None):
    if not targets:
        return entries

    available_tools = {e["tool"] for e in entries}
    unknown = [t for t in targets if t not in available_tools]
    if unknown:
        raise ValueError(f"Unknown tool(s) specified: {', '.join(unknown)}")

    return [e for e in entries if e["tool"] in targets]


def install(entries):
    """
    Install all entries from repo_path → system_path.
    If system_path exists, move it to .old before overwriting.
    """
    for entry in entries:
        repo_path = Path(entry["repo_path"]).expanduser()
        system_path = Path(entry["system_path"]).expanduser()
        backup_path = system_path.with_name(system_path.name + ".old")

        # Backup existing file/folder
        if system_path.exists():
            if backup_path.exists():
                shutil.rmtree(backup_path) if backup_path.is_dir() else backup_path.unlink()
            system_path.rename(backup_path)

        # Copy from repo to system
        if entry["type"] == "dir":
            shutil.copytree(repo_path, system_path)
        else:  # file
            system_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(repo_path, system_path)


def store(entries):
    """
    Store all entries: system_path → repo_path (strict sync).
    - For directories: remove repo folder first
    - For files: overwrite
    """
    for entry in entries:
        repo_path = Path(entry["repo_path"]).expanduser()
        system_path = Path(entry["system_path"]).expanduser()

        if entry["type"] == "dir":
            if repo_path.exists():
                shutil.rmtree(repo_path)
            shutil.copytree(system_path, repo_path)
        else:  # file
            repo_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(system_path, repo_path)


def restore(entries):
    """
    Restore all entries from .old → system_path.
    """
    for entry in entries:
        system_path = Path(entry["system_path"]).expanduser()
        backup_path = system_path.with_name(system_path.name + ".old")

        if backup_path.exists():
            if system_path.exists():
                if system_path.is_dir():
                    shutil.rmtree(system_path)
                else:
                    system_path.unlink()
            backup_path.rename(system_path)


def clean(entries):
    """
    Remove all .old backups created during install.
    """
    for entry in entries:
        system_path = Path(entry["system_path"]).expanduser()
        backup_path = system_path.with_name(system_path.name + ".old")

        if backup_path.exists():
            if backup_path.is_dir():
                shutil.rmtree(backup_path)
            else:
                backup_path.unlink()


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Manage dotfiles from TOML config")
    parser.add_argument(
        "action",
        choices=["install", "store", "restore", "clean", "bundle"],
        help="Action to perform"
    )
    parser.add_argument(
        "targets",
        nargs="*",
        help="Tools to act on (default: all)"
    )

    args = parser.parse_args()

    entries = parse_dotfiles_toml(CONFIG_FILE)
    entries = filter_entries(entries, args.targets)

    if args.action == "install":
        install(entries)
    elif args.action == "store":
        store(entries)
    elif args.action == "restore":
        restore(entries)
    elif args.action == "clean":
        clean(entries)
    elif args.action == "bundle":
        tools = "-".join(sorted(list({entry['tool'] for entry in entries})))
        bundle_name = f"{datetime.now().strftime('%Y-%m-%d-%H-%M')}-{tools}"
        create_tool_bundle(bundle_name, entries)
