"""
`bundle` subcommand for `hoptctl`
"""
from __future__ import annotations

from pathlib import Path
from typing import Optional

from rich.console import Group, RenderableType
from rich.progress import MofNCompleteColumn, Progress, SpinnerColumn, Task
from rich.table import Table
from rich.text import Text
from rich.tree import Tree
from typer import Argument, Context, Option, Typer

from hoppr import main, processor
from hoppr.cli.layout import HopprBasePanel, HopprJobsPanel, HopprLayout

app = Typer(
    context_settings={"allow_interspersed_args": True, "help_option_names": ["-h", "--help"]},
    help="Run the stages specified in the transfer config file on the content specified in the manifest",
    invoke_without_command=True,
    no_args_is_help=True,
    rich_markup_mode="markdown",
    subcommand_metavar="",
)


_SUCCESS_CHECK = "[green]\u2714"
_FAIL_X = "[red]\u274c"


class HopprBundleSpinnerColumn(SpinnerColumn):
    """
    Customized SpinnerColumn that displays either red "X" or green check mark based on task result
    """

    def render(self, task: Task) -> RenderableType:
        status = task.fields.get("status")
        self.finished_text = Text.from_markup(_FAIL_X if status == "fail" else _SUCCESS_CHECK)

        return self.finished_text if task.finished else self.spinner.render(task.get_time())


class HopprBundleJobsPanel(HopprJobsPanel):
    """
    Customized Rich Progress bar Panel
    """

    progress_bar = Progress(
        "{task.description}",
        MofNCompleteColumn(),
        HopprBundleSpinnerColumn(style="[white]progress.spinner"),
        expand=True,
    )

    def __init__(self) -> None:
        super().__init__()
        self.renderable = self.progress_bar
        self.title = "[bold blue]Components"


class HopprBundleLayout(HopprLayout):
    """
    Layout of the `hopctl bundle` console application
    """

    name: str = "root"
    jobs_panel = HopprBundleJobsPanel()


class HopprBundleSummary(HopprBasePanel):
    """
    `hopctl bundle` results summary
    """

    stage_results_map: dict[str, Table] = {}

    def __init__(self, *args, title: str | None = "Summary", **kwargs) -> None:
        self.summary_group = Group()
        self.failure_table = Table("Plugin", "Component", "Details", box=None, pad_edge=False, show_edge=False)

        self.total_success_count = 0
        self.total_failure_count = 0

        super().__init__(self.summary_group, title, *args, **kwargs)

    def add_failure(self, plugin_name: str, comp_str: str | None, message: str) -> None:
        """
        Add an entry to the failures table

        Args:
            plugin_name (str): Name of the plugin that failed
            comp_str (str | None): Name of the failed component
            message (str): Result message
        """
        self.failure_table.add_row(plugin_name, comp_str or "", message)
        self.total_failure_count += 1
        self.total_success_count -= 1

    def add_method_result(self, stage_name: str, method_name: str, result_count: int, failure_count: int) -> None:
        """
        Add an entry to the results table for a plugin method

        Args:
            stage_name (str): Name of the stage
            method_name (str): Name of the method
            result_count (int): Total number of results
            failure_count (int): Number of failed results
        """
        results_table = self.stage_results_map[stage_name]
        results_table.add_row(method_name, str(result_count - failure_count), str(failure_count))

    def add_stage_result(self, stage_name: str) -> Tree:
        """
        Add a results table for the specified stage to the panel

        Args:
            stage_name (str): Name of the stage

        Returns:
            Tree: Object containing nested results table
        """
        self.stage_results_map[stage_name] = Table(
            "Step",
            "# Success",
            "# Fail",
            box=None,
            pad_edge=False,
            show_edge=False,
        )

        stage_summary = Tree(label=f"Stage: {stage_name}", guide_style="conceal")
        stage_summary.add(self.stage_results_map[stage_name])

        return stage_summary


def manifest_callback(ctx: Context, manifest_file: Path) -> Path:  # pragma: no cover
    """
    Add `manifest_file` value to shared Click context. Allows interspersed CLI options/arguments
    """
    ctx.params["manifest_file"] = manifest_file

    return manifest_file


@app.callback()
def bundle(  # pylint: disable=unused-argument, too-many-arguments
    manifest_file: Path = Argument(
        ...,
        callback=manifest_callback,
        dir_okay=False,
        exists=True,
        help="Path to manifest file",
        resolve_path=True,
        show_default=False,
    ),
    credentials_file: Optional[Path] = Option(
        None,
        "-c",
        "--credentials",
        help="Specify credentials config for services",
        envvar="HOPPR_CREDS_CONFIG",
    ),
    transfer_file: Path = Option(
        "transfer.yml",
        "-t",
        "--transfer",
        help="Specify transfer config",
        envvar="HOPPR_TRANSFER_CONFIG",
    ),
    log_file: Optional[Path] = Option(
        None,
        "-l",
        "--log",
        help="File to which log will be written",
        envvar="HOPPR_LOG_FILE",
    ),
    verbose: bool = Option(
        False,
        "-v",
        "--debug",
        "--verbose",
        help="Enable debug output",
    ),
    strict_repos: bool = Option(
        True,
        "--strict/--no-strict",
        help="Utilize only manifest repositories for package collection",
        envvar="HOPPR_STRICT_REPOS",
    ),
    create_attestations: bool = Option(
        False,
        "-a",
        "--attest",
        help="Generate in-toto attestations",
        envvar="HOPPR_ATTESTATION",
    ),
    functionary_key_path: Optional[Path] = Option(
        None,
        "-fk",
        "--functionary-key",
        help="Path to key used to sign in-toto layout",
        envvar="HOPPR_FUNCTIONARY_KEY",
    ),
    functionary_key_prompt: bool = Option(
        False,
        "-p",
        "--prompt",
        help="Prompt user for project owner key's password",
        envvar="HOPPR_PROJECT_OWNER_KEY_PROMPT",
    ),
    functionary_key_password: str = Option(
        None,
        "-fk-pass",
        "--project-owner-key-password",
        help="Password for project owner key",
        envvar="HOPPR_PROJECT_OWNER_KEY_PASSWORD",
    ),
    previous_delivery: Optional[Path] = Option(
        None,
        "-pd",
        "--previous-delivery",
        help="Path to manifest or tar bundle representing a previous delivery",
        envvar="HOPPR_PREVIOUS_DELIVERY",
    ),
):  # pragma: no cover
    """
    Run the stages specified in the transfer config
    file on the content specified in the manifest
    """
    processor.layout = HopprBundleLayout()
    processor.summary_panel = HopprBundleSummary()

    main.bundle(**locals())
