"""Action executor base class and result types.

Provides the abstract interface that provider-specific action executors must
implement. Lives in core (not server) so business logic stays testable
without database dependencies.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any


@dataclass
class RuleResult:
    """Outcome of executing a single rule."""

    rule_name: str
    success: bool
    items_matched: int = 0
    items_affected: int = 0
    items_skipped: int = 0
    error: str | None = None
    details: dict[str, Any] = field(default_factory=dict)


@dataclass
class ActionResult:
    """Aggregate outcome of executing an action (all rules)."""

    success: bool
    rules_processed: int = 0
    rules_succeeded: int = 0
    rules_failed: int = 0
    total_items_affected: int = 0
    rule_results: list[RuleResult] = field(default_factory=list)
    error: str | None = None

    def to_dict(self) -> dict[str, Any]:
        """Serialize to a JSON-compatible dict for storage."""
        return {
            "success": self.success,
            "rules_processed": self.rules_processed,
            "rules_succeeded": self.rules_succeeded,
            "rules_failed": self.rules_failed,
            "total_items_affected": self.total_items_affected,
            "rule_results": [
                {
                    "rule_name": r.rule_name,
                    "success": r.success,
                    "items_matched": r.items_matched,
                    "items_affected": r.items_affected,
                    "items_skipped": r.items_skipped,
                    "error": r.error,
                    "details": r.details,
                }
                for r in self.rule_results
            ],
            "error": self.error,
        }


class ActionExecutor(ABC):
    """Abstract base class for provider-specific action executors.

    Each provider that supports actions implements a concrete executor.
    The executor receives rule configs (plain dicts) and performs the
    actual mutations on the external service.
    """

    @abstractmethod
    async def execute(
        self,
        action_type: str,
        rules: list[dict[str, Any]],
        config: dict[str, Any],
    ) -> ActionResult:
        """Execute the action with given rules.

        Args:
            action_type: The action type key (e.g. "auto_organize").
            rules: List of rule_config dicts (from ActionRule rows).
            config: Action-level config dict.

        Returns:
            ActionResult with per-rule outcomes.
        """

    @abstractmethod
    async def validate_rules(
        self,
        action_type: str,
        rules: list[dict[str, Any]],
    ) -> list[str]:
        """Validate rules before saving.

        Returns a list of error messages. Empty list means valid.
        """

    @abstractmethod
    async def dry_run(
        self,
        action_type: str,
        rules: list[dict[str, Any]],
        config: dict[str, Any],
    ) -> ActionResult:
        """Preview what would happen without mutating anything.

        Returns the same ActionResult shape as execute(), but
        items_affected reflects what *would* be changed.
        """
