# 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, archive token management commands.

These are "porcelain": they implement opinionated operations on
:collection:`debian:archive` and :collection:`debian:suite` collections,
rather than basic "plumbing" operations.
"""

import abc
import argparse
from collections.abc import Iterable
from typing import override

import rich
from rich.table import Table

from debusine.artifacts.models import CollectionCategory
from debusine.client.commands.base import ModelCommand, WorkspaceCommand
from debusine.client.models import (
    CollectionRoles,
    CollectionTokenGrantRequest,
    CollectionTokenGrantResponse,
)


class TokenCommand(
    ModelCommand[CollectionTokenGrantResponse], WorkspaceCommand, abc.ABC
):
    """Common infrastructure for archive token commands."""

    @override
    def __init__(
        self, parser: argparse.ArgumentParser, args: argparse.Namespace
    ) -> None:
        super().__init__(parser, args)
        if args.suite is not None:
            self.collection_category = CollectionCategory.SUITE
            self.collection_name = args.suite
        else:
            self.collection_category = CollectionCategory.ARCHIVE
            self.collection_name = "_"

    @override
    @classmethod
    def configure(cls, parser: argparse.ArgumentParser) -> None:
        super().configure(parser)
        parser.add_argument(
            "--suite",
            help="Operate only on this suite, rather than the whole archive",
        )

    @override
    def _list_rich(
        self, grants: Iterable[CollectionTokenGrantResponse]
    ) -> None:
        table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD)
        table.add_column("Token type")
        table.add_column("Token hash")
        table.add_column("Username")
        table.add_column("Comment")
        table.add_column("Role")
        for grant in grants:
            table.add_row(
                grant.token_type,
                grant.token_hash,
                grant.username,
                grant.comment,
                grant.role,
            )
        rich.print(table)

    @override
    def _show_rich(self, grant: CollectionTokenGrantResponse) -> None:
        table = Table(box=rich.box.SIMPLE, show_header=False)
        table.add_row("Token type:", grant.token_type)
        table.add_row("Token hash:", grant.token_hash)
        table.add_row("Token key (only shown once):", grant.token_key)
        table.add_row("Username:", grant.username)
        table.add_row("Comment:", grant.comment)
        table.add_row("Role:", grant.role)
        rich.print(table)


class TokenGrant(TokenCommand, name="grant", group="archive.token"):
    """Grant a role on an archive to a token or an external username."""

    @override
    @classmethod
    def configure(cls, parser: argparse.ArgumentParser) -> None:
        super().configure(parser)
        parser.add_argument("--token", help="Hash of an existing token")
        parser.add_argument(
            "--external-username", help="External username for a new token"
        )
        parser.add_argument(
            "--comment", default="", help="Comment for a new token"
        )
        parser.add_argument(
            "role",
            choices=sorted(str(role) for role in CollectionRoles),
            help="Role to grant",
        )

    @override
    def run(self) -> None:
        request_data = {"comment": self.args.comment, "role": self.args.role}
        if self.args.token is not None:
            request_data["token_hash"] = self.args.token
        if self.args.external_username is not None:
            request_data["username"] = self.args.external_username
        with self._api_call_or_fail():
            grant = self.debusine.collection_token_grant_create(
                workspace=self.workspace,
                collection_category=self.collection_category,
                collection_name=self.collection_name,
                request=CollectionTokenGrantRequest(**request_data),
            )

        self.show(grant)


class TokenList(TokenCommand, name="list", group="archive.token"):
    """List tokens that have been granted roles on an archive."""

    @override
    def run(self) -> None:
        with self._api_call_or_fail():
            self.list(
                self.debusine.collection_token_grant_iter(
                    workspace=self.workspace,
                    collection_category=self.collection_category,
                    collection_name=self.collection_name,
                )
            )


class TokenRevoke(TokenCommand, name="revoke", group="archive.token"):
    """Revoke a role on an archive from a token."""

    @override
    @classmethod
    def configure(cls, parser: argparse.ArgumentParser) -> None:
        super().configure(parser)
        parser.add_argument("token", help="Hash of a token to revoke")
        parser.add_argument(
            "role",
            choices=sorted(str(role) for role in CollectionRoles),
            help="Role to revoke",
        )

    @override
    def run(self) -> None:
        with self._api_call_or_fail():
            self.debusine.collection_token_grant_delete(
                workspace=self.workspace,
                collection_category=self.collection_category,
                collection_name=self.collection_name,
                token_hash=self.args.token,
                role=self.args.role,
            )

        self.feedback(
            f"Role '{self.args.role}' revoked from {self.args.token}."
        )
