"""Base classes for composable remote test suites."""

from __future__ import annotations

import time
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from tests.remote.runner import RemoteTestRunner


class StepStatus(str, Enum):
    PASSED = "passed"
    FAILED = "failed"
    SKIPPED = "skipped"
    ERROR = "error"


@dataclass
class TestStep:
    """A single step within a test suite."""

    __test__ = False  # Prevent pytest from collecting this as a test class

    name: str
    description: str
    status: StepStatus = StepStatus.SKIPPED
    stdout: str = ""
    stderr: str = ""
    duration_s: float = 0.0
    error_message: str = ""


@dataclass
class SuiteResult:
    """Aggregate result of running a test suite."""

    suite_name: str
    device_name: str
    steps: list[TestStep] = field(default_factory=list)
    total_duration_s: float = 0.0
    started_at: str = ""
    finished_at: str = ""

    @property
    def passed(self) -> int:
        return sum(1 for s in self.steps if s.status == StepStatus.PASSED)

    @property
    def failed(self) -> int:
        return sum(1 for s in self.steps if s.status == StepStatus.FAILED)

    @property
    def errors(self) -> int:
        return sum(1 for s in self.steps if s.status == StepStatus.ERROR)

    @property
    def skipped(self) -> int:
        return sum(1 for s in self.steps if s.status == StepStatus.SKIPPED)

    @property
    def success(self) -> bool:
        return self.failed == 0 and self.errors == 0


class BaseSuite(ABC):
    """Abstract base class for composable test suites."""

    name: str = "base"
    description: str = "Base suite"

    async def run(self, runner: RemoteTestRunner, device_name: str = "unknown") -> SuiteResult:
        """Execute all steps in the suite and return aggregated results."""
        from datetime import datetime, timezone

        result = SuiteResult(
            suite_name=self.name,
            device_name=device_name,
            started_at=datetime.now(tz=timezone.utc).isoformat(),
        )

        start = time.monotonic()
        try:
            await self.execute(runner, result)
        except Exception as exc:
            # If the suite itself crashes, record it as an error step
            step = TestStep(
                name="suite_crash",
                description=f"Unexpected error in suite '{self.name}'",
                status=StepStatus.ERROR,
                error_message=str(exc),
            )
            result.steps.append(step)

        result.total_duration_s = round(time.monotonic() - start, 3)
        result.finished_at = datetime.now(tz=timezone.utc).isoformat()
        return result

    @abstractmethod
    async def execute(self, runner: RemoteTestRunner, result: SuiteResult) -> None:
        """Implement the suite steps. Append TestStep objects to result.steps."""

    def _add_step(
        self,
        result: SuiteResult,
        name: str,
        description: str,
        *,
        status: StepStatus,
        stdout: str = "",
        stderr: str = "",
        duration_s: float = 0.0,
        error_message: str = "",
    ) -> TestStep:
        """Helper to create and append a test step."""
        step = TestStep(
            name=name,
            description=description,
            status=status,
            stdout=stdout,
            stderr=stderr,
            duration_s=duration_s,
            error_message=error_message,
        )
        result.steps.append(step)
        return step
