#!/usr/bin/env python3
"""
Equivalent of `make new` / `make astro` / `make update` from the ubonn-thesis Makefile.

Usage:
    python thesis_util.py new    [--thesis DIRNAME] [--type physics|astro] [--subfile]
    python thesis_util.py update [--thesis DIRNAME]

Subcommands:
    new     Create a new thesis skeleton (default type: physics).
    update  Update an existing thesis with the latest package files
            (ubonn-thesis.sty, ubonn-biblatex.sty, standard_refs-biber.bib,
            cover/, figs/cover/).

Defaults:
    --thesis  mythesis
    --type    physics
"""

import argparse
import difflib
import os
import re
import shutil
import sys


def sed_replace(src_path: str, dst_path: str, pattern: str, replacement: str) -> None:
    """Read src_path, replace pattern with replacement, write to dst_path."""

    # print(f'Looking for {pattern} in {src_path}')
    # print(f'Replacing with {replacement} in {dst_path}')
    with open(src_path, 'r', encoding='utf-8') as f:
        content = f.read()
    # if re.search(pattern, content, re.MULTILINE):
    #     print('Pattern found')
    # else:
    #     print('Pattern not found')
    content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
    with open(dst_path, 'w', encoding='utf-8') as f:
        f.write(content)


def create_thesis(thesis: str, thesis_type: str, subfile: bool) -> None:
    subfile_dir = '_subfile' if subfile else ''
    skel_dir = f'thesis{subfile_dir}_skel'

    # ── Create directory structure ──────────────────────────────────────────
    for d in [thesis, os.path.join(thesis, 'bib'), os.path.join(thesis, 'figs')]:
        os.mkdir(d)
        print(f'Created directory: {d}')

    # ── Copy / sed skeleton files ───────────────────────────────────────────

    # Main thesis .tex file (plain copy)
    shutil.copy(
        os.path.join(skel_dir, f'thesis_{thesis_type}_skel.tex'),
        os.path.join(thesis, f'{thesis}.tex'),
    )
    print(f'Copied: {thesis}/{thesis}.tex')

    # Files that need MYTHESISMAIN → thesis name substitution
    for src_name, dst_name in [
        (f'thesis_{thesis_type}_intro.tex',    'thesis_intro.tex'),
        (f'thesis_{thesis_type}_appendix.tex', 'thesis_appendix.tex'),
        ('thesis_acknowledge.tex',             'thesis_acknowledge.tex'),
    ]:
        sed_replace(
            os.path.join(skel_dir, src_name),
            os.path.join(thesis, dst_name),
            'MYTHESISMAIN',
            thesis,
        )
        print(f'Processed (sed): {thesis}/{dst_name}')

    # Plain copies from thesis_skel/
    for filename in [
        'thesis_defs.sty',
        'thesis_cv.tex',
        'cover_only.tex',
        'summary_only.tex',
        'abstract_only.tex',
    ]:
        shutil.copy(os.path.join('thesis_skel', filename), os.path.join(thesis, filename))
        print(f'Copied: {thesis}/{filename}')

    # Root-level .sty files
    for filename in ['ubonn-thesis.sty', 'ubonn-biblatex.sty']:
        shutil.copy(filename, os.path.join(thesis, filename))
        print(f'Copied: {thesis}/{filename}')

    # Bibliography files
    shutil.copy(
        os.path.join('thesis_skel', 'thesis_refs.bib'),
        os.path.join(thesis, 'bib', 'thesis_refs.bib'),
    )
    shutil.copy(
        os.path.join('refs', 'standard_refs-biber.bib'),
        os.path.join(thesis, 'bib', 'standard_refs-biber.bib'),
    )
    print(f'Copied bib files to {thesis}/bib/')

    # Directories copied recursively
    shutil.copytree('cover', os.path.join(thesis, 'cover'))
    print(f'Copied: {thesis}/cover/')

    shutil.copytree(os.path.join('figs', 'cover'), os.path.join(thesis, 'figs', 'cover'))
    print(f'Copied: {thesis}/figs/cover/')

    # Makefile with THESIS variable updated
    sed_replace(
        os.path.join('thesis_skel', 'Makefile'),
        os.path.join(thesis, 'Makefile'),
        r'^THESIS \?=.*',
        f'THESIS ?= {thesis}',
    )
    print(f'Processed (sed): {thesis}/Makefile')

    print(f"\nDone! New thesis skeleton created in '{thesis}/'")


_TEXT_EXTENSIONS = {
    '.tex', '.sty', '.bib', '.txt', '.md', '.cfg', '.cls', '.dtx', '.ins',
    '.py', '.sh', '.mk', 'Makefile',
}


def _is_text_file(path: str) -> bool:
    """Return True if the file should be compared as text."""
    _, ext = os.path.splitext(path)
    return ext.lower() in _TEXT_EXTENSIONS or os.path.basename(path) in _TEXT_EXTENSIONS


def _files_identical(a: str, b: str) -> bool:
    """Return True if two files have identical content."""
    with open(a, 'rb') as fa, open(b, 'rb') as fb:
        return fa.read() == fb.read()


def _show_diff(src: str, dst: str) -> None:
    """Print a unified diff between dst (existing) and src (new)."""
    with open(dst, 'r', encoding='utf-8', errors='replace') as f:
        dst_lines = f.readlines()
    with open(src, 'r', encoding='utf-8', errors='replace') as f:
        src_lines = f.readlines()
    diff = difflib.unified_diff(
        dst_lines, src_lines,
        fromfile=f'{dst} (current)',
        tofile=f'{src} (new)',
    )
    sys.stdout.writelines(diff)


def _prompt_overwrite(src: str, dst: str) -> bool:
    """Show a diff (or binary summary) and ask the user whether to overwrite.

    Returns True if the file should be copied.
    """
    if _files_identical(src, dst):
        print(f'Unchanged: {dst}')
        return False

    print(f'\n{"─" * 60}')
    if _is_text_file(src):
        print(f'Differences in {dst}:')
        _show_diff(src, dst)
    else:
        src_size = os.path.getsize(src)
        dst_size = os.path.getsize(dst)
        print(f'Binary file differs: {dst}')
        print(f'  current : {dst_size} bytes')
        print(f'  new     : {src_size} bytes')
    print(f'{"─" * 60}')

    while True:
        answer = input(f'Overwrite {dst}? [y/n/q] ').strip().lower()
        if answer in ('y', 'yes'):
            return True
        if answer in ('n', 'no'):
            return False
        if answer in ('q', 'quit'):
            print('Aborted.')
            sys.exit(0)
        print("Please enter 'y' (yes), 'n' (no), or 'q' (quit).")


def _copy_with_prompt(src: str, dst: str) -> None:
    """Copy src to dst.  If dst already exists, diff first and prompt."""
    if not os.path.exists(src):
        print(f'Warning: source file not found, skipping: {src}', file=sys.stderr)
        return
    if os.path.exists(dst):
        if not _prompt_overwrite(src, dst):
            print(f'Skipped: {dst}')
            return
    shutil.copy2(src, dst)
    print(f'Copied: {dst}')


def _copytree_with_prompt(src: str, dst: str) -> None:
    """Recursively copy a directory tree, diffing and prompting for each changed file."""
    if not os.path.isdir(src):
        print(f'Warning: source directory not found, skipping: {src}', file=sys.stderr)
        return
    os.makedirs(dst, exist_ok=True)
    for root, _dirs, files in os.walk(src):
        rel_root = os.path.relpath(root, src)
        dst_root = os.path.join(dst, rel_root) if rel_root != '.' else dst
        os.makedirs(dst_root, exist_ok=True)
        for filename in files:
            _copy_with_prompt(
                os.path.join(root, filename),
                os.path.join(dst_root, filename),
            )


def update_thesis(thesis: str) -> None:
    """Equivalent of `make update THESIS=<thesis>`.

    For each file that would be overwritten, shows a unified diff (text files)
    or a size summary (binary files) and asks for confirmation before copying.
    New files (not yet present in the thesis directory) are copied silently.

    Files updated:
        ubonn-thesis.sty             → <thesis>/
        ubonn-biblatex.sty           → <thesis>/
        refs/standard_refs-biber.bib → <thesis>/bib/
        cover/                       → <thesis>/cover/        (recursive)
        figs/cover/                  → <thesis>/figs/cover/   (recursive)

    The commented-out Makefile sed step from the original Makefile is
    intentionally omitted here too.
    """
    if not os.path.isdir(thesis):
        print(f"Error: thesis directory '{thesis}' does not exist.", file=sys.stderr)
        sys.exit(1)

    _copy_with_prompt('ubonn-thesis.sty',
                      os.path.join(thesis, 'ubonn-thesis.sty'))
    _copy_with_prompt('ubonn-biblatex.sty',
                      os.path.join(thesis, 'ubonn-biblatex.sty'))
    _copy_with_prompt(os.path.join('refs', 'standard_refs-biber.bib'),
                      os.path.join(thesis, 'bib', 'standard_refs-biber.bib'))

    _copytree_with_prompt('cover',
                          os.path.join(thesis, 'cover'))
    _copytree_with_prompt(os.path.join('figs', 'cover'),
                          os.path.join(thesis, 'figs', 'cover'))

    print(f"\nDone! '{thesis}/' updated.")


def main() -> None:
    parser = argparse.ArgumentParser(
        description='Manage a ubonn thesis skeleton.',
    )
    subparsers = parser.add_subparsers(dest='command', metavar='COMMAND')

    # ── new / astro subcommand ───────────────────────────────────────────────
    new_parser = subparsers.add_parser(
        'new',
        help='Create a new thesis skeleton (equivalent of `make new` / `make astro`).',
    )
    new_parser.add_argument(
        '--thesis', default='mythesis',
        help='Name of the thesis directory (default: mythesis)',
    )
    new_parser.add_argument(
        '--type', choices=['physics', 'astro'], default='physics',
        help='Thesis type: physics (default) or astro',
    )
    new_parser.add_argument(
        '--subfile', action='store_true',
        help='Use the _subfile skeleton variant (sets SUBFILE=1)',
    )

    # ── update subcommand ────────────────────────────────────────────────────
    upd_parser = subparsers.add_parser(
        'update',
        help='Update an existing thesis with the latest package files '
             '(equivalent of `make update`). Diffs changed files and prompts '
             'before overwriting.',
    )
    upd_parser.add_argument(
        '--thesis', default='mythesis',
        help='Name of the existing thesis directory (default: mythesis)',
    )

    args = parser.parse_args()

    if args.command == 'new':
        if os.path.exists(args.thesis):
            print(f"Error: directory '{args.thesis}' already exists.", file=sys.stderr)
            sys.exit(1)
        create_thesis(thesis=args.thesis, thesis_type=args.type, subfile=args.subfile)

    elif args.command == 'update':
        update_thesis(thesis=args.thesis)

    else:
        parser.print_help()
        sys.exit(1)


if __name__ == '__main__':
    main()
