"""
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 QuiltRunner(Tool):
    """Tool to manage Debian patches with quilt."""

    name = "quilt_runner"
    description = (
        "Manage the Debian patch queue (debian/patches) with quilt. "
        "Read-only operations run directly: 'series' (list all patches), "
        "'applied' (list applied patches), 'unapplied' (list unapplied "
        "patches), 'top' (name of the topmost applied patch), 'header' (show a "
        "patch's header). State-changing operations require confirmation: "
        "'push' (apply patches; use all=true for -a), 'pop' (unapply patches; "
        "all=true for -a), 'new' (create a new patch named by 'argument'), "
        "'refresh' (regenerate the top patch after edits), 'import' (import an "
        "external patch file given by 'argument'), 'delete' (remove a patch). "
        "QUILT_PATCHES defaults to debian/patches."
    )

    READONLY_OPS = {"series", "applied", "unapplied", "top", "header"}
    ARG_REQUIRED_OPS = {"new", "import", "delete", "header"}
    ALL_OPS = READONLY_OPS | {"push", "pop", "new", "refresh", "import", "delete"}

    def needs_confirmation(self, args_dict: dict) -> tuple:
        op = args_dict.get("operation", "")
        if op in self.READONLY_OPS:
            return False, ""
        return True, f"Modify the patch queue (quilt {op})"

    def confirmation_context(self, args_dict: dict) -> str:
        cmd = self._build_cmd(args_dict)
        return "Command to run:\n" + " ".join(shlex.quote(c) for c in cmd)

    def _build_cmd(self, args_dict: dict) -> list:
        op = args_dict.get("operation", "")
        argument = args_dict.get("argument")
        cmd = ["quilt", op]
        if op in ("push", "pop") and args_dict.get("all"):
            cmd.append("-a")
        if args_dict.get("extra_args"):
            cmd.extend(shlex.split(args_dict["extra_args"]))
        if argument:
            cmd.append(argument)
        return cmd

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

    def __call__(
        self,
        operation: str,
        argument: str = None,
        all: bool = False,
        path: str = ".",
        extra_args: str = None,
    ) -> str:
        try:
            if operation not in self.ALL_OPS:
                return (
                    f"Error: unknown operation '{operation}'. Valid: "
                    f"{', '.join(sorted(self.ALL_OPS))}."
                )
            if operation in self.ARG_REQUIRED_OPS and (
                not argument or not argument.strip()
            ):
                return f"Error: operation '{operation}' requires an 'argument'."

            cwd = os.path.expanduser(path)
            if not os.path.isdir(cwd):
                return f"Error: '{cwd}' does not exist."

            if operation == "import" and argument:
                argument = os.path.expanduser(argument)

            args_dict = {
                "operation": operation,
                "argument": argument,
                "all": all,
                "extra_args": extra_args,
            }
            cmd = self._build_cmd(args_dict)

            env = {**os.environ, "LC_ALL": "C"}
            env.setdefault("QUILT_PATCHES", "debian/patches")
            try:
                result = subprocess.run(
                    cmd, env=env, cwd=cwd, capture_output=True, text=True, timeout=60
                )
            except FileNotFoundError:
                return "Error: 'quilt' is not installed (package: quilt)."
            except subprocess.TimeoutExpired:
                return "Error: quilt 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": [
                    "series",
                    "applied",
                    "unapplied",
                    "top",
                    "header",
                    "push",
                    "pop",
                    "new",
                    "refresh",
                    "import",
                    "delete",
                ],
                "description": "The quilt operation to perform.",
            },
            "argument": {
                "type": "string",
                "description": "Patch name (new/delete/header) or path to a patch "
                "file (import). Required for new, import, delete, "
                "and header.",
            },
            "all": {
                "type": "boolean",
                "description": "For push/pop, operate on all patches (-a).",
            },
            "path": {
                "type": "string",
                "description": "Package source directory. Defaults to '.'.",
            },
            "extra_args": {
                "type": "string",
                "description": "Additional arguments passed directly to quilt.",
            },
        },
        "required": ["operation"],
    }
