# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Debusine command line interface, workspace management commands."""

import argparse
import sys
from typing import Any

from debusine.client.commands.base import WorkspaceCommand
from debusine.client.exceptions import DebusineError
from debusine.client.models import WorkspaceInheritanceChain
from debusine.utils.input import YamlEditor


class Inheritance(WorkspaceCommand, group="workspace"):
    """Query and change the inheritance chain of a workspace."""

    def __init__(self, args: argparse.Namespace) -> None:
        """Handle workspace also as a (deprecated) positional argument."""
        super().__init__(args)
        # Override with the workspace given as positional argument, if present
        if self.args.workspace_arg:
            self.workspace = self.args.workspace_arg

    @classmethod
    def configure(cls, parser: argparse.ArgumentParser) -> None:
        """Configure the ArgumentParser for this subcommand."""
        super().configure(parser)
        parser.add_argument("workspace_arg", nargs="?", help="workspace name")
        parser.add_argument(
            "--append",
            type=str,
            nargs="+",
            help="workspaces (id, 'name', or 'scope/name')"
            " to add to the end of the parent list",
        )
        parser.add_argument(
            "--prepend",
            type=str,
            nargs="+",
            help="workspaces (id, 'name', or 'scope/name')"
            " to add to the beginning of the parent list",
        )
        parser.add_argument(
            "--remove",
            type=str,
            nargs="+",
            help="workspaces (id, 'name', or 'scope/name')"
            " to remove from the parent list",
        )
        parser.add_argument(
            "--set",
            type=str,
            nargs="+",
            help="workspaces (id, 'name', or 'scope/name')"
            " that compose the new parent list",
        )
        parser.add_argument(
            "--edit",
            action="store_true",
            help="edit the parent list using $EDITOR",
        )

    @classmethod
    def _print_server_error(cls, error: DebusineError) -> None:
        """Print a server error in YAML."""
        description: dict[str, Any] = {
            "result": "failure",
            "error": error.asdict(),
        }
        cls._print_yaml(description, file=sys.stderr)

    def _apply_inheritance(
        self,
        *,
        workspace: str,
        chain: WorkspaceInheritanceChain,
    ) -> WorkspaceInheritanceChain:
        """
        Set inheritance.

        If API call fails because of an error not caused by the parameters
         fail.

        :raises DebusineError: if an error setting the inheritance happens
          (e.g. invalid chain because workspace does not exist).
        """
        with self._api_call_or_fail():
            return self.debusine.set_workspace_inheritance(
                workspace, chain=chain
            )

    def _edit_inheritance(
        self,
        workspace: str,
        current: WorkspaceInheritanceChain,
    ) -> WorkspaceInheritanceChain:
        """Run the interactive YAML edit flow and return new inheritance."""
        editor: YamlEditor[list[Any]] = YamlEditor(
            current.dict()["chain"],
            help_text=[
                "Here you can configure the inheritance"
                f" of workspace '{workspace}'.",
                "",
                "This is a YAML list of dictionaries with"
                " 'id/scope/workspace' keys.",
                "Usually you specify the workspace,"
                " and optionally the scope. The scope",
                "is only required to refer to a workspace"
                " outside of the current scope.",
                "",
                "For example:",
                "",
                "- workspace: base",
                "- scope: some-other-scope",
                "  workspace: foobar",
            ],
        )

        while True:
            if editor.edit():
                new = WorkspaceInheritanceChain(chain=editor.value)
                if new == current:
                    return new

                try:
                    return self._apply_inheritance(
                        workspace=workspace, chain=new
                    )
                except DebusineError as error:
                    print("Error setting inheritance:\n", file=sys.stderr)
                    self._print_server_error(error)
                    answer = editor.confirm(
                        "\nDo you want to retry the same edit?"
                    )
                    if not answer:
                        return current

            else:
                return current

    def run(self) -> None:
        """Run the command."""
        workspace = self.workspace
        with self._api_call_or_fail():
            try:
                current = self.debusine.get_workspace_inheritance(workspace)
            except DebusineError as err:
                output = {"result": "failure", "error": err.asdict()}
                self._print_yaml(output, file=sys.stderr)
                raise SystemExit(3)

        new = current
        if self.args.append:
            new = new + WorkspaceInheritanceChain.from_strings(self.args.append)
        if self.args.prepend:
            new = (
                WorkspaceInheritanceChain.from_strings(self.args.prepend) + new
            )
        if self.args.remove:
            new = new - WorkspaceInheritanceChain.from_strings(self.args.remove)
        if self.args.set:
            new = WorkspaceInheritanceChain.from_strings(self.args.set)
        if self.args.edit:
            new = self._edit_inheritance(workspace, current)
            self._print_yaml(new.dict())
            # self._edit_inheritance() applies the inheritance, all done
            return

        if new != current:
            try:
                new = self._apply_inheritance(workspace=workspace, chain=new)
            except DebusineError as error:
                print("Error setting inheritance:\n", file=sys.stderr)
                self._print_server_error(error)
                raise SystemExit(3)

        self._print_yaml(new.dict())


class LegacyInheritance(
    Inheritance,
    name="workspace-inheritance",
    deprecated="see `workspace inheritance`",
):
    """Query and change the inheritance chain of a workspace."""
