# 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, task configuration management commands."""

import argparse
from pathlib import Path
from typing import override

from debusine.client.commands.base import DebusineCommand, WorkspaceCommand
from debusine.client.task_configuration import (
    InvalidRepository,
    LocalTaskConfigurationRepository,
)


class Pull(WorkspaceCommand, group="task-config"):
    """
    Check out a task configuration collection.

    Update a local checkout if it exists.
    """

    local_repo: LocalTaskConfigurationRepository
    collection: str

    @override
    @classmethod
    def configure(cls, parser: argparse.ArgumentParser) -> None:
        super().configure(parser)
        parser.add_argument(
            "collection",
            type=str,
            nargs="?",
            help="collection name (not needed to refresh an existing checkout)",
        )
        parser.add_argument(
            "--workdir",
            type=Path,
            default=Path.cwd(),
            help="working directory to use. Default: current directory.",
        )

    def __init__(
        self, parser: argparse.ArgumentParser, args: argparse.Namespace
    ) -> None:
        """Initialize a local repository."""
        super().__init__(parser, args)

        if not LocalTaskConfigurationRepository.is_checkout(self.args.workdir):
            self.args.workdir.mkdir(exist_ok=True)
            self.local_repo = LocalTaskConfigurationRepository.from_path(
                self.args.workdir
            )
            self.collection = self.args.collection or "default"
        else:
            self.local_repo = LocalTaskConfigurationRepository.from_path(
                self.args.workdir
            )
            manifest = self.local_repo.manifest
            assert manifest is not None
            if self.workspace_arg_set:
                if self.workspace != manifest.workspace:
                    self._fail(
                        f"--workspace={self.workspace} is provided,"
                        " but the repository checked out refers to"
                        f" workspace {manifest.workspace!r}"
                    )
            else:
                self.workspace = manifest.workspace

            if (
                self.args.collection is not None
                and self.args.collection != manifest.collection.name
            ):
                self._fail(
                    f"--collection={self.args.collection} is provided,"
                    " but the repository checked out refers to"
                    f" collection {manifest.collection.name!r}"
                )
            self.collection = manifest.collection.name

    @override
    def run(self) -> None:
        server_repo = self.debusine.fetch_task_configuration_collection(
            workspace=self.workspace, name=self.collection
        )
        try:
            stats = self.local_repo.pull(
                server_repo, logger=self.debusine._logger
            )
        except InvalidRepository as e:
            self._fail(str(e))
        self.debusine._logger.info(
            "%d added, %d updated, %d deleted, %d unchanged",
            stats.added,
            stats.updated,
            stats.deleted,
            stats.unchanged,
        )


class LegacyPull(
    Pull, name="task-config-pull", deprecated="see `task-config pull`"
):
    """
    Check out a task configuration collection.

    Update a local checkout if it exists.
    """


class Push(DebusineCommand, group="task-config"):
    """
    Send local task configuration data to Debusine.

    Replace the contents of the collection on the server with those of the
    local checkout.
    """

    @override
    @classmethod
    def configure(cls, parser: argparse.ArgumentParser) -> None:
        super().configure(parser)
        parser.add_argument(
            "--workdir",
            type=Path,
            default=Path.cwd(),
            help="working directory to use. Default: current directory.",
        )
        parser.add_argument(
            "-n",
            "--dry-run",
            action="store_true",
            help="do not update the collection:"
            " only show what would be updated",
        )
        parser.add_argument(
            "-f",
            "--force",
            action="store_true",
            help="push the collection, skipping all checks",
        )

    @override
    def run(self) -> None:
        workdir = self.args.workdir
        dry_run = self.args.dry_run
        force = self.args.force
        if not LocalTaskConfigurationRepository.is_checkout(workdir):
            self._fail(f"{workdir} is not a repository")
        local_repo = LocalTaskConfigurationRepository.from_path(workdir)
        assert local_repo.manifest is not None

        if local_repo.is_dirty():
            message = (
                f"{workdir} has uncommitted changes:"
                " please commit them before pushing"
            )
            if force:
                self.debusine._logger.warning("%s", message)
            else:
                self._fail(message)

        server_repo = self.debusine.fetch_task_configuration_collection(
            workspace=local_repo.manifest.workspace,
            name=local_repo.manifest.collection.name,
        )

        server_git_commit = server_repo.manifest.collection.data.get(
            "git_commit"
        )
        if server_git_commit is not None and not local_repo.has_commit(
            server_git_commit
        ):
            message = (
                "server collection was pushed from commit"
                f" {server_git_commit} which is not known to {workdir}"
            )
            if force:
                self.debusine._logger.warning("%s", message)
            else:
                self._fail(message)

        if (new_git_commit := local_repo.git_commit()) is not None:
            local_repo.manifest.collection.data["git_commit"] = new_git_commit
        else:
            local_repo.manifest.collection.data.pop("git_commit", None)

        if dry_run:
            self.debusine._logger.info("Pushing data to server (dry run)...")
        else:
            self.debusine._logger.info("Pushing data to server...")

        results = self.debusine.push_task_configuration_collection(
            repo=local_repo, dry_run=dry_run
        )
        self.debusine._logger.info(
            "%d added, %d updated, %d removed, %d unchanged",
            results.added,
            results.updated,
            results.removed,
            results.unchanged,
        )


class LegacyPush(
    Push, name="task-config-push", deprecated="see `task-config push`"
):
    """
    Send local task configuration data to Debusine.

    Replace the contents of the collection on the server with those of the
    local checkout.
    """
