#!/usr/bin/env python3
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2022 Kenneth Loafman
#
# This file is part of duplicity.
#
# Duplicity is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# Duplicity is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with duplicity; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

import subprocess
import sys
import re
from pathlib import Path


def run_command(command, check=True, capture_output=True):
    try:
        result = subprocess.run(command, capture_output=capture_output, text=True, check=check)
        if capture_output:
            return result.stdout.strip()
        return None
    except subprocess.CalledProcessError as e:
        if capture_output:
            print(f"Error running command: {' '.join(command)}")
            print(e.stderr)
        sys.exit(e.returncode)


def sort_entries(entries):
    priority = {"new": 0, "chg": 1, "fix": 2}

    def get_priority(line):
        match = re.search(r":\s*(new|chg|fix):", line, re.IGNORECASE)
        if match:
            return priority.get(match.group(1).lower(), 3)
        return 3

    return sorted(entries, key=get_priority)


def process_line(line, delete_patterns):
    if any(pattern.search(line) for pattern in delete_patterns):
        return None

    # Replacements
    line = re.sub(r":\s*- ", ": ", line)
    line = re.sub(r":\s*\* ", ": ", line)
    line = re.sub(r"\s+", " ", line)

    # Complex replacement
    # s/^(.*):\s*(ch.|fix|new):(dev|usr|pkg|test|doc):\s*(.*)/\1:\2: \4/ig
    line = re.sub(r"^(.*):\s*(ch.|fix|new):(dev|usr|pkg|test|doc):\s*(.*)", r"\1:\2: \4", line, flags=re.IGNORECASE)

    # Link commit hash
    line = re.sub(
        r"^\s*\*\s+([0-9a-f]{8}):", r" * [`\1`](https://gitlab.com/duplicity/duplicity/-/commit/\1):", line, count=1
    )

    return line


def main():
    if len(sys.argv) != 1:
        print(f"usage: {sys.argv[0]}")
        sys.exit(2)

    # Get version
    version = run_command([sys.executable, "./setup.py", "--version"])
    print(f"{sys.argv[0]} of {version}")

    # Git configs
    run_command(["git", "config", "set", "changelog.format", "  * %h: %s"])
    run_command(["git", "config", "set", "core.editor", "emacs"])

    print("Generate CHANGELOG.md")
    # Run git changelog
    changelog_cmd = ["git", "changelog", "--all", "--prune-old", "--no-merges", "--tag", "(Unreleased)"]
    import os

    env = os.environ.copy()
    env["GIT_EDITOR"] = "true"

    with open("CHANGELOG.md", "w") as f:
        subprocess.run(changelog_cmd, stdout=f, check=True, env=env)

    print("Apply some filters")
    # Filters from the original script
    delete_patterns = [
        re.compile(r"update changelog", re.IGNORECASE),
        re.compile(r"prep for", re.IGNORECASE),
        re.compile(r"version for lp", re.IGNORECASE),
        re.compile(r"crowdin update", re.IGNORECASE),
        re.compile(r"bump version", re.IGNORECASE),
        re.compile(r"change version", re.IGNORECASE),
        re.compile(r"run po/update.pot", re.IGNORECASE),
        re.compile(r"!minor\s*", re.IGNORECASE),
        re.compile(r":test|doc:", re.IGNORECASE),
    ]

    changelog_path = Path("CHANGELOG.md")
    lines = changelog_path.read_text().splitlines()

    processed_lines = []
    for line in lines:
        processed = process_line(line, delete_patterns)
        if processed is not None:
            processed_lines.append(processed)

    blocks = []
    current_block = {"header": [], "entries": [], "footer": []}

    for i, line in enumerate(processed_lines):
        # A header is followed by a line of '='
        if i + 1 < len(processed_lines) and processed_lines[i + 1].startswith("====="):
            if current_block["header"] or current_block["entries"]:
                blocks.append(current_block)
            current_block = {"header": [line], "entries": [], "footer": []}
            continue

        if line.startswith("====="):
            current_block["header"].append(line)
            continue

        if line.strip().startswith("* "):
            current_block["entries"].append(line)
            continue

        if current_block["entries"]:
            current_block["footer"].append(line)
        else:
            current_block["header"].append(line)

    blocks.append(current_block)

    new_lines = []
    for block in blocks:
        new_lines.extend(block["header"])
        new_lines.extend(sort_entries(block["entries"]))
        new_lines.extend(block["footer"])

    changelog_path.write_text("\n".join(new_lines) + "\n")

    # find out if anything has changed
    status = run_command(["git", "status", "--porcelain"])
    if not status:
        print("*** NOTHING CHANGED, NO COMMITS ***")
        sys.exit(0)

    print(status, file=sys.stderr)

    # make sure only CHANGELOG.md has changed
    other_changes = [line for line in status.splitlines() if " M CHANGELOG.md" not in line]
    if other_changes:
        print("*** REPO DIRTY, DO NOT COMMIT ***")
        sys.exit(1)


if __name__ == "__main__":
    main()
