#!/usr/bin/env python3
"""
Fix broken wiki links in knowledge-base repo by rewriting them as proper
source-relative paths (the format Forgejo/Gitea Markdown rendering expects).

Bug classes handled:
A. [[wiki/...]]  → strip 'wiki/' prefix, then resolve source-relative
B. [[concepts/...]] from wrong directory → resolve to ../concepts/...
C. [[concepts/...]] from same directory → resolve to basename
D. [[plain-name.md]] from wrong directory → resolve to ../.../plain-name.md
"""

import os
import re
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
WIKI_DIR = REPO_ROOT / "wiki"
LINK_RE = re.compile(r'\[\[([^\]|]+)(?:\|([^\]]+))?\]\]')


def find_wiki_files():
    files = []
    for root, dirs, filenames in os.walk(WIKI_DIR):
        for f in filenames:
            if f.endswith('.md'):
                files.append(Path(root) / f)
    return sorted(files)


def find_target(link_path: str):
    """
    Given a link path as written, find the actual target file.
    Supports wiki/, raw/, and root-relative interpretations.
    Returns absolute Path to the target file, or None if not found.
    """
    basename = Path(link_path).name
    if not basename.endswith('.md'):
        basename_candidate = basename + '.md'
    else:
        basename_candidate = basename

    candidates = []

    # Interpretation 1: path starts with "wiki/" — strip and look in WIKI_DIR
    if link_path.startswith('wiki/'):
        stripped = link_path[5:]
        if not stripped.endswith('.md'):
            stripped += '.md'
        target = WIKI_DIR / stripped
        if target.exists():
            candidates.append(target)

    # Interpretation 2: path starts with "raw/" — look in REPO_ROOT/raw/
    if link_path.startswith('raw/'):
        target = REPO_ROOT / link_path
        if not target.exists() and not link_path.endswith('.md'):
            target = REPO_ROOT / (link_path + '.md')
        if target.exists():
            candidates.append(target)

    # Interpretation 3: path contains "/" — interpret as root-relative (but skip if starts with ../)
    if '/' in link_path and not link_path.startswith('../'):
        # Try as wiki/-root-relative
        if not link_path.endswith('.md'):
            root_relative = WIKI_DIR / (link_path + '.md')
        else:
            root_relative = WIKI_DIR / link_path
        candidates.append(root_relative)
        # Also try as repo-root-relative
        if not link_path.endswith('.md'):
            candidates.append(REPO_ROOT / (link_path + '.md'))
        else:
            candidates.append(REPO_ROOT / link_path)

    # Interpretation 4: plain basename — find by basename
    if '/' not in link_path:
        matches = [m for m in WIKI_DIR.rglob(basename_candidate) if 'test-fixtures' not in str(m)]
        # Also check raw/
        matches += [m for m in (REPO_ROOT / 'raw').rglob(basename_candidate) if 'test-fixtures' not in str(m)]
        matches += [m for m in REPO_ROOT.rglob(basename_candidate) if 'test-fixtures' not in str(m)]
        if len(matches) == 1:
            candidates.append(matches[0])
        elif len(matches) > 1:
            non_fixture = [m for m in matches if 'test-fixtures' not in str(m)]
            if len(non_fixture) == 1:
                candidates.append(non_fixture[0])

    # Interpretation 5: explicit ../-relative path — try to resolve as-is,
    # then look for the basename at any matching location in wiki/ or raw/
    if link_path.startswith('../'):
        # Find the actual basename (last component, with .md suffix if needed)
        path_parts = Path(link_path).parts
        search_basename = path_parts[-1]
        if not search_basename.endswith('.md'):
            search_basename += '.md'
        # Find all files with this name in wiki/
        matches = [m for m in WIKI_DIR.rglob(search_basename) if 'test-fixtures' not in str(m)]
        if len(matches) == 1:
            candidates.append(matches[0])
        elif len(matches) > 1:
            non_fixture = [m for m in matches if 'test-fixtures' not in str(m)]
            if len(non_fixture) == 1:
                candidates.append(non_fixture[0])

    # Try each candidate
    for c in candidates:
        if c.exists() and 'test-fixtures' not in str(c):
            return c

    return None


def make_source_relative(from_file: Path, to_file: Path) -> str:
    """Make a relative path from from_file's directory to to_file."""
    rel = os.path.relpath(to_file, start=from_file.parent)
    return rel


def fix_link(link_path: str, source_file: Path) -> str:
    """Fix a broken wiki link. Returns the corrected link path."""
    if link_path.startswith('http'):
        return link_path

    # Strip anchor
    anchor = ''
    if '#' in link_path:
        link_path, anchor = link_path.split('#', 1)
        anchor = '#' + anchor

    # Already valid? (source-relative resolves correctly)
    target = (source_file.parent / link_path).resolve()
    if target.exists():
        return link_path + anchor

    # Try with .md appended
    if not link_path.endswith('.md'):
        target = (source_file.parent / (link_path + '.md')).resolve()
        if target.exists():
            return link_path + '.md' + anchor

    # Find the actual target file by interpreting the link in different ways
    actual_target = find_target(link_path)
    if actual_target and actual_target != source_file:
        corrected = make_source_relative(source_file, actual_target)
        return corrected + anchor

    return link_path + anchor  # no fix found


def process_file(source_file: Path, dry_run=False):
    content = source_file.read_text(encoding='utf-8')

    def repl(match):
        link_path = match.group(1).strip()
        label = match.group(2)
        fixed = fix_link(link_path, source_file)
        if fixed != link_path:
            if label:
                return f'[[{fixed}|{label}]]'
            return f'[[{fixed}]]'
        return match.group(0)

    new_content = LINK_RE.sub(repl, content)

    if new_content != content:
        if not dry_run:
            source_file.write_text(new_content, encoding='utf-8')
        return True
    return False


def main():
    dry_run = '--dry-run' in sys.argv
    files = find_wiki_files()
    fixed_count = 0
    fixed_files = []

    for f in files:
        if process_file(f, dry_run=dry_run):
            fixed_count += 1
            fixed_files.append(f.relative_to(REPO_ROOT))

    print(f"{'Would fix' if dry_run else 'Fixed'}: {fixed_count} file(s)")
    if fixed_files:
        for rel in fixed_files:
            print(f"  - {rel}")
    if dry_run and fixed_count > 0:
        print("\nRun without --dry-run to apply changes.")


if __name__ == '__main__':
    main()