"""Rules for installing Claude Code plugins."""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import ClassVar

from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
from pants.engine.fs import Digest
from pants.engine.internals.selectors import Get
from pants.engine.process import FallibleProcessResult, Process, ProcessResult
from pants.engine.rules import collect_rules, rule
from pants.engine.target import WrappedTarget, WrappedTargetRequest
from pants.util.logging import LogLevel

from pants_claude_plugins.subsystem import ClaudePluginsSubsystem
from pants_claude_plugins.targets import (
    ClaudePlugin,
    EnabledField,
    MarketplaceField,
    PluginNameField,
    ScopeField,
)

logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class ClaudePluginInstallRequest:
    """Request to install a single Claude Code plugin."""

    plugin_name: str
    marketplace: str
    scope: str


@dataclass(frozen=True)
class ClaudePluginInstallResult:
    """Result of installing a Claude Code plugin."""

    plugin_name: str
    marketplace: str
    scope: str
    success: bool
    output: str
    error: str = ""

    @property
    def full_name(self) -> str:
        """Return the full plugin@marketplace identifier."""
        return f"{self.plugin_name}@{self.marketplace}"


@dataclass(frozen=True)
class ClaudePluginBatchInstallRequest:
    """Request to install multiple Claude Code plugins."""

    plugins: tuple[ClaudePluginInstallRequest, ...]


@dataclass(frozen=True)
class ClaudePluginBatchInstallResult:
    """Result of installing multiple Claude Code plugins."""

    results: tuple[ClaudePluginInstallResult, ...]

    @property
    def all_successful(self) -> bool:
        """Return True if all installations were successful."""
        return all(r.success for r in self.results)

    @property
    def failed(self) -> tuple[ClaudePluginInstallResult, ...]:
        """Return only the failed installations."""
        return tuple(r for r in self.results if not r.success)

    @property
    def succeeded(self) -> tuple[ClaudePluginInstallResult, ...]:
        """Return only the successful installations."""
        return tuple(r for r in self.results if r.success)


@rule(level=LogLevel.DEBUG)
async def install_claude_plugin(
    request: ClaudePluginInstallRequest,
    subsystem: ClaudePluginsSubsystem,
) -> ClaudePluginInstallResult:
    """Install a single Claude Code plugin using the claude CLI."""
    full_name = f"{request.plugin_name}@{request.marketplace}"

    if subsystem.dry_run:
        return ClaudePluginInstallResult(
            plugin_name=request.plugin_name,
            marketplace=request.marketplace,
            scope=request.scope,
            success=True,
            output=f"[DRY RUN] Would install {full_name} with scope={request.scope}",
        )

    # Build the claude plugin install command
    args = [
        subsystem.claude_binary,
        "plugin",
        "install",
        full_name,
        "--scope",
        request.scope,
    ]

    process = Process(
        argv=args,
        description=f"Installing Claude plugin {full_name}",
        level=LogLevel.INFO,
    )

    result = await Get(FallibleProcessResult, Process, process)

    if result.exit_code == 0:
        return ClaudePluginInstallResult(
            plugin_name=request.plugin_name,
            marketplace=request.marketplace,
            scope=request.scope,
            success=True,
            output=result.stdout.decode("utf-8"),
        )
    else:
        return ClaudePluginInstallResult(
            plugin_name=request.plugin_name,
            marketplace=request.marketplace,
            scope=request.scope,
            success=False,
            output=result.stdout.decode("utf-8"),
            error=result.stderr.decode("utf-8"),
        )


@rule
async def install_claude_plugins_batch(
    request: ClaudePluginBatchInstallRequest,
    subsystem: ClaudePluginsSubsystem,
) -> ClaudePluginBatchInstallResult:
    """Install multiple Claude Code plugins."""
    results = []

    for plugin_request in request.plugins:
        result = await Get(ClaudePluginInstallResult, ClaudePluginInstallRequest, plugin_request)
        results.append(result)

    return ClaudePluginBatchInstallResult(results=tuple(results))


@dataclass(frozen=True)
class ClaudePluginFromTargetRequest:
    """Request to create a plugin install request from a target."""

    wrapped_target: WrappedTarget


@rule
async def plugin_request_from_target(
    request: ClaudePluginFromTargetRequest,
    subsystem: ClaudePluginsSubsystem,
) -> ClaudePluginInstallRequest:
    """Convert a ClaudePlugin target to an install request."""
    target = request.wrapped_target.target

    plugin_name = target[PluginNameField].value
    marketplace = target[MarketplaceField].value
    scope = target[ScopeField].value or subsystem.default_scope

    return ClaudePluginInstallRequest(
        plugin_name=plugin_name,
        marketplace=marketplace,
        scope=scope,
    )


# Marketplace management


@dataclass(frozen=True)
class MarketplaceAddRequest:
    """Request to add a marketplace."""

    source: str  # GitHub owner/repo, git URL, or local path


@dataclass(frozen=True)
class MarketplaceAddResult:
    """Result of adding a marketplace."""

    source: str
    success: bool
    output: str
    error: str = ""


@rule(level=LogLevel.DEBUG)
async def add_marketplace(
    request: MarketplaceAddRequest,
    subsystem: ClaudePluginsSubsystem,
) -> MarketplaceAddResult:
    """Add a marketplace using the claude CLI."""
    if subsystem.dry_run:
        return MarketplaceAddResult(
            source=request.source,
            success=True,
            output=f"[DRY RUN] Would add marketplace: {request.source}",
        )

    # Build the claude marketplace add command
    args = [
        subsystem.claude_binary,
        "plugin",
        "marketplace",
        "add",
        request.source,
    ]

    process = Process(
        argv=args,
        description=f"Adding marketplace {request.source}",
        level=LogLevel.INFO,
    )

    result = await Get(FallibleProcessResult, Process, process)

    if result.exit_code == 0:
        return MarketplaceAddResult(
            source=request.source,
            success=True,
            output=result.stdout.decode("utf-8"),
        )
    else:
        return MarketplaceAddResult(
            source=request.source,
            success=False,
            output=result.stdout.decode("utf-8"),
            error=result.stderr.decode("utf-8"),
        )


@dataclass(frozen=True)
class MarketplaceBatchAddRequest:
    """Request to add multiple marketplaces."""

    sources: tuple[str, ...]


@dataclass(frozen=True)
class MarketplaceBatchAddResult:
    """Result of adding multiple marketplaces."""

    results: tuple[MarketplaceAddResult, ...]

    @property
    def all_successful(self) -> bool:
        """Return True if all additions were successful."""
        return all(r.success for r in self.results)

    @property
    def failed(self) -> tuple[MarketplaceAddResult, ...]:
        """Return only the failed additions."""
        return tuple(r for r in self.results if not r.success)

    @property
    def succeeded(self) -> tuple[MarketplaceAddResult, ...]:
        """Return only the successful additions."""
        return tuple(r for r in self.results if r.success)


@rule
async def add_marketplaces_batch(
    request: MarketplaceBatchAddRequest,
    subsystem: ClaudePluginsSubsystem,
) -> MarketplaceBatchAddResult:
    """Add multiple marketplaces."""
    results = []

    for source in request.sources:
        result = await Get(MarketplaceAddResult, MarketplaceAddRequest(source))
        results.append(result)

    return MarketplaceBatchAddResult(results=tuple(results))


def rules():
    """Return all rules for this module."""
    return collect_rules()
