"""SeedRunner and ResetRunner — execute and revert fixture providers.

SeedRunner resolves dependencies and executes providers in topological order.
ResetRunner reverses the order for clean teardown.
Both track execution history in FixtureRun records.
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Any

from django.db import transaction

from simorgh.apps.provisioning.models import FixtureRun
from simorgh.apps.provisioning.resolver import DependencyResolver

if TYPE_CHECKING:
    from simorgh.apps.provisioning.providers import FixtureProvider, ProviderResult


class SeedRunner:
    """Executes fixture providers in dependency-resolved order.

    Usage:
        runner = SeedRunner()
        runner.register(tenant_provider)
        runner.register(user_provider)  # depends_on=["tenant_provider"]
        results = runner.run(context={"tenant": my_tenant})
    """

    def __init__(self, batch_atomic: bool = True) -> None:
        self._resolver = DependencyResolver()
        self._batch_atomic = batch_atomic
        self._results: list[ProviderResult] = []

    def register(self, provider: FixtureProvider) -> None:
        self._resolver.register(provider)

    def register_bulk(self, providers: list[FixtureProvider]) -> None:
        self._resolver.register_bulk(providers)

    def run(
        self,
        context: dict[str, Any] | None = None,
        *,
        dry_run: bool = False,
        tenant_slug: str = "",
        workspace_slug: str = "",
    ) -> list[ProviderResult]:
        """Execute all registered providers in resolved order.

        Args:
            context: Dict with 'tenant', 'workspace', 'org_node', 'faker', etc.
            dry_run: If True, only compute what would run without persisting.
            tenant_slug: For tracking in FixtureRun record.
            workspace_slug: For tracking in FixtureRun record.

        Returns:
            List of ProviderResult, one per executed provider.
        """
        if context is None:
            context = {}

        ordered = self._resolver.resolve()
        self._results = []

        if dry_run:
            for provider in ordered:
                preview = provider.dry_run(context)
                self._results.append(
                    ProviderResult(
                        provider_name=provider.name,
                        details=preview,
                    )
                )
            return self._results

        start = time.monotonic()
        total_created = 0
        total_updated = 0

        def _execute_all() -> None:
            nonlocal total_created, total_updated
            for provider in ordered:
                result = provider.provide(context)
                self._results.append(result)
                total_created += result.created
                total_updated += result.updated

        if self._batch_atomic:
            with transaction.atomic():
                _execute_all()
        else:
            _execute_all()

        duration_ms = int((time.monotonic() - start) * 1000)

        FixtureRun.objects.create(
            kind=FixtureRun.RunKind.SEED,
            tenant_slug=tenant_slug,
            workspace_slug=workspace_slug,
            fixture_names=[p.name for p in ordered],
            items_created=total_created,
            items_updated=total_updated,
            duration_ms=duration_ms,
            success=True,
        )

        return self._results

    @property
    def results(self) -> list[ProviderResult]:
        return self._results

    @property
    def total_created(self) -> int:
        return sum(r.created for r in self._results)

    @property
    def total_updated(self) -> int:
        return sum(r.updated for r in self._results)

    def reset(self) -> None:
        self._resolver.reset()
        self._results.clear()


class ResetRunner:
    """Executes reset operations in reverse dependency order.

    Usage:
        runner = ResetRunner()
        runner.register(user_provider)
        runner.register(tenant_provider)
        runner.reset(context={"tenant": my_tenant})
    """

    def __init__(self) -> None:
        self._resolver = DependencyResolver()
        self._results: list[ProviderResult] = []

    def register(self, provider: FixtureProvider) -> None:
        self._resolver.register(provider)

    def register_bulk(self, providers: list[FixtureProvider]) -> None:
        self._resolver.register_bulk(providers)

    def run(
        self,
        context: dict[str, Any] | None = None,
        *,
        dry_run: bool = False,
        tenant_slug: str = "",
        workspace_slug: str = "",
    ) -> list[ProviderResult]:
        """Execute reset for all registered providers in reverse order.

        Returns:
            List of ProviderResult, one per reset provider.
        """
        if context is None:
            context = {}

        ordered = self._resolver.reverse_order()
        self._results = []

        if dry_run:
            return [
                ProviderResult(
                    provider_name=p.name,
                    details=p.dry_run(context),
                )
                for p in ordered
            ]

        start = time.monotonic()
        total_deleted = 0

        with transaction.atomic():
            for provider in ordered:
                result = provider.reset(context)
                self._results.append(result)
                total_deleted += result.deleted

        duration_ms = int((time.monotonic() - start) * 1000)

        FixtureRun.objects.create(
            kind=FixtureRun.RunKind.RESET,
            tenant_slug=tenant_slug,
            workspace_slug=workspace_slug,
            fixture_names=[p.name for p in ordered],
            items_deleted=total_deleted,
            duration_ms=duration_ms,
            success=True,
        )

        return self._results

    def reset(self) -> None:
        self._resolver.reset()
        self._results.clear()
