"""
Copyright (C) 2024-2026 Mo Zhou <lumin@debian.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

import difflib
import os
import pty
import re
import select
import shlex
import subprocess
import tempfile
from collections import deque

from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from rich.text import Text

from .base import Tool, _diff_stat, _short, render_diff


class DchRunner(Tool):
    """Tool to edit debian/changelog via dch (devscripts)."""

    name = "dch_runner"
    description = (
        "Add or modify entries in debian/changelog using dch (debchange). "
        "Runs non-interactively (no editor) by using the provided message. "
        "Supported operations: 'increment' (dch -i: new entry with an "
        "incremented version), 'newversion' (dch -v: new entry with an "
        "explicit version, supplied via 'version'), 'append' (dch -a: add a "
        "line to the current, unreleased entry), 'release' (dch -r: finalize "
        "the top entry for release, setting the distribution and timestamp). "
        "This modifies debian/changelog and requires user confirmation."
    )

    def needs_confirmation(self, args_dict: dict) -> tuple:
        op = args_dict.get("operation", "")
        return True, f"Modify debian/changelog (dch {op})"

    def confirmation_context(self, args_dict: dict) -> str:
        path = os.path.expanduser(args_dict.get("path", ".") or ".")
        changelog = os.path.join(path, "debian", "changelog")
        cmd = self._build_cmd(args_dict)
        ctx = "Command to run:\n" + " ".join(shlex.quote(c) for c in cmd)
        try:
            with open(changelog) as f:
                head = "".join(f.readlines()[:15])
            ctx += f"\n\nCurrent top of {changelog}:\n{head}"
        except OSError:
            ctx += f"\n\n(Could not read {changelog}.)"
        return ctx

    def _build_cmd(self, args_dict: dict) -> list:
        operation = args_dict.get("operation", "")
        cmd = ["dch"]
        if operation == "increment":
            cmd.append("-i")
        elif operation == "newversion":
            cmd.extend(["-v", args_dict.get("version") or ""])
        elif operation == "append":
            cmd.append("-a")
        elif operation == "release":
            cmd.append("-r")
        if args_dict.get("distribution"):
            cmd.extend(["--distribution", args_dict["distribution"]])
        if args_dict.get("extra_args"):
            cmd.extend(shlex.split(args_dict["extra_args"]))
        # A trailing message argument keeps dch from launching an editor.
        message = args_dict.get("message")
        if operation == "release":
            cmd.append(message if message else "")
        elif message:
            cmd.append(message)
        return cmd

    def summary(self, args_dict: dict) -> str:
        return f"dch {args_dict.get('operation', '')}".rstrip()

    def __call__(
        self,
        operation: str,
        message: str = None,
        version: str = None,
        distribution: str = None,
        path: str = ".",
        extra_args: str = None,
    ) -> str:
        try:
            if operation not in ("increment", "newversion", "append", "release"):
                return (
                    f"Error: unknown operation '{operation}'. Valid: "
                    "increment, newversion, append, release."
                )
            if operation == "newversion" and not version:
                return "Error: operation 'newversion' requires 'version'."
            if operation in ("increment", "newversion", "append") and not message:
                return f"Error: operation '{operation}' requires 'message'."

            cwd = os.path.expanduser(path)
            if not os.path.isfile(os.path.join(cwd, "debian", "changelog")):
                return (
                    f"Error: no debian/changelog found under '{cwd}'. "
                    "Run this from a package source tree."
                )

            args_dict = {
                "operation": operation,
                "message": message,
                "version": version,
                "distribution": distribution,
                "extra_args": extra_args,
            }
            cmd = self._build_cmd(args_dict)

            # EDITOR/VISUAL are neutralized so dch can never block on an editor.
            env = {**os.environ, "LC_ALL": "C", "EDITOR": "true", "VISUAL": "true"}
            try:
                result = subprocess.run(
                    cmd, env=env, cwd=cwd, capture_output=True, text=True, timeout=60
                )
            except FileNotFoundError:
                return "Error: 'dch' is not installed (package: devscripts)."
            except subprocess.TimeoutExpired:
                return "Error: dch timed out after 60s."

            out = result.stdout
            if result.stderr:
                out += "\nSTDERR:\n" + result.stderr
            if len(out) > 4000:
                out = out[:2000] + "\n...(truncated output)...\n" + out[-2000:]

            ret = f"Status: {result.returncode}\n"
            ret += f"Output:\n{out}\n" if out.strip() else "No output.\n"
            return ret
        except Exception as e:
            return f"Error: {str(e)}"

    parameters = {
        "type": "object",
        "properties": {
            "operation": {
                "type": "string",
                "enum": ["increment", "newversion", "append", "release"],
                "description": "The changelog operation to perform.",
            },
            "message": {
                "type": "string",
                "description": "The changelog entry text. Required for increment, "
                "newversion, and append; optional for release.",
            },
            "version": {
                "type": "string",
                "description": "The new version string. Required for 'newversion'.",
            },
            "distribution": {
                "type": "string",
                "description": "Target distribution (dch --distribution), e.g. "
                "'unstable' or 'experimental'.",
            },
            "path": {
                "type": "string",
                "description": "Package source directory containing debian/. "
                "Defaults to '.'.",
            },
            "extra_args": {
                "type": "string",
                "description": "Additional arguments passed directly to dch.",
            },
        },
        "required": ["operation"],
    }
