"""Base classes for Playwright scenario execution.

Scenarios are deterministic, reusable browser automation sequences
that execute specific UI workflows against tenant applications.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional

from playwright.async_api import Page


@dataclass
class ScenarioOptions:
    """Configuration options for scenario execution.

    Attributes:
        timeout_ms: Default timeout for actions in milliseconds
        screenshot_on_failure: Capture screenshot when scenario fails
        screenshot_on_success: Capture screenshot when scenario succeeds
        save_trace: Save Playwright trace for debugging
        allowed_domains: List of domains the scenario can access (REQUIRED for security)
        artifacts_dir: Directory to save screenshots and traces
    """
    timeout_ms: int = 60000
    screenshot_on_failure: bool = True
    screenshot_on_success: bool = False
    save_trace: bool = False
    allowed_domains: list[str] = field(default_factory=list)
    artifacts_dir: Optional[Path] = None

    def __post_init__(self) -> None:
        if self.artifacts_dir and isinstance(self.artifacts_dir, str):
            self.artifacts_dir = Path(self.artifacts_dir)


@dataclass
class ScenarioResult:
    """Result of a scenario execution.

    Attributes:
        success: Whether the scenario completed successfully
        data: Scenario-specific result data
        screenshots: List of paths to captured screenshots
        error: Error message if scenario failed
        trace_path: Path to trace file if tracing was enabled
    """
    success: bool
    data: dict[str, Any]
    screenshots: list[str] = field(default_factory=list)
    error: Optional[str] = None
    trace_path: Optional[str] = None


class BaseScenario(ABC):
    """Abstract base class for Playwright scenarios.

    Each scenario implements a specific UI automation workflow.
    Scenarios are registered by name and dispatched by the PlaywrightExecutor.

    Example implementation:
        class NextcloudInitialSetup(BaseScenario):
            @property
            def name(self) -> str:
                return "nextcloud_initial_setup"

            @property
            def required_inputs(self) -> list[str]:
                return ["base_url", "admin_username", "admin_password"]

            async def execute(self, page, inputs, options) -> ScenarioResult:
                # Perform setup steps...
                return ScenarioResult(success=True, data={"setup": "complete"})
    """

    @property
    @abstractmethod
    def name(self) -> str:
        """Unique name identifying this scenario.

        This name is used in task payloads to select the scenario.
        Convention: lowercase_with_underscores (e.g., 'nextcloud_initial_setup')
        """
        ...

    @property
    @abstractmethod
    def required_inputs(self) -> list[str]:
        """List of required input keys for this scenario.

        The executor validates that all required inputs are present
        before executing the scenario.
        """
        ...

    @property
    def optional_inputs(self) -> list[str]:
        """List of optional input keys for this scenario.

        Override this property to declare optional inputs with defaults.
        """
        return []

    @property
    def description(self) -> str:
        """Human-readable description of what this scenario does.

        Override this property to provide documentation.
        """
        return f"Scenario: {self.name}"

    @abstractmethod
    async def execute(
        self,
        page: Page,
        inputs: dict[str, Any],
        options: ScenarioOptions,
    ) -> ScenarioResult:
        """Execute the scenario against the provided page.

        Args:
            page: Playwright Page object with domain restrictions applied
            inputs: Dictionary of input values (validated by executor)
            options: Scenario options including timeout and artifact settings

        Returns:
            ScenarioResult with success status and any result data

        Note:
            - Domain restrictions are already enforced by the executor
            - Screenshots on failure are handled by the executor
            - Focus on the business logic of the UI workflow
        """
        ...

    async def setup(self, page: Page, options: ScenarioOptions) -> None:
        """Optional setup hook called before execute().

        Override to perform setup actions like setting viewport size,
        configuring page settings, etc.
        """
        pass

    async def teardown(self, page: Page, options: ScenarioOptions) -> None:
        """Optional teardown hook called after execute().

        Override to perform cleanup actions. Called even if execute() fails.
        """
        pass

    def validate_inputs(self, inputs: dict[str, Any]) -> list[str]:
        """Validate inputs and return list of missing required keys.

        Args:
            inputs: Dictionary of inputs to validate

        Returns:
            List of missing required input keys (empty if all present)
        """
        return [key for key in self.required_inputs if key not in inputs]
