"""Provider-neutral atomic durable ingestion over immutable Activity source truth."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable

from mountain_twin.activity.contracts import Activity, ActivitySourceKind, RawProviderRecord, User

from .contracts import new_activity_revision, new_raw_provider_record, raw_payload_sha256
from .repository import SqliteActivityIdentityStore

ActivityNormalizer = Callable[[RawProviderRecord, dict[str, Any]], Activity]


@dataclass(frozen=True)
class PersistentIngestionRequest:
    """One already-acquired provider source record; no network operation belongs here."""

    user: User
    source_kind: ActivitySourceKind
    provider: str | None
    source_activity_id: str
    payload: dict[str, Any]
    record_kind: str
    fetched_at: str
    source_schema_version: str | None
    normalizer: ActivityNormalizer
    taxonomy_version: str


@dataclass(frozen=True)
class PersistentIngestionResult:
    """Non-sensitive durable identities and idempotency facts for one source record."""

    activity_entity_id: str
    source_link_id: str
    raw_record_id: str
    activity_revision_id: str
    entity_created: bool
    raw_payload_created: bool
    raw_record_created: bool
    revision_created: bool
    provenance_attached: bool
    current_revision_changed: bool


class PersistentActivityIngestionService:
    """Coordinate source truth, canonical normalization and revision persistence atomically."""

    def __init__(self, store: SqliteActivityIdentityStore):
        self.store = store

    def ingest(self, request: PersistentIngestionRequest) -> PersistentIngestionResult:
        """Persist one acquired source record without fetching, retries or provider-specific SQL."""
        with self.store.transaction():
            entity, source_link, entity_created = self.store.resolve_or_create_entity_source_link(
                request.user,
                request.source_kind,
                request.provider,
                request.source_activity_id,
                request.fetched_at,
            )
            raw_payload_created = _is_missing(
                self.store.get_raw_payload_blob, raw_payload_sha256(request.payload)
            )
            raw_payload = self.store.put_raw_payload(request.payload)
            pending_raw = new_raw_provider_record(
                source_link,
                raw_payload,
                request.record_kind,
                request.fetched_at,
                request.source_schema_version,
            )
            raw_record_created = _is_missing(
                self.store.get_raw_provider_record, pending_raw.raw_record_id
            )
            raw_record = self.store.insert_raw_provider_record(source_link.source_link_id, pending_raw)

            canonical_activity = request.normalizer(raw_record, request.payload)
            pending_revision = new_activity_revision(
                entity.activity_entity_id, canonical_activity, request.taxonomy_version
            )
            revision_created = _is_missing(
                self.store.get_revision, pending_revision.activity_revision_id
            )
            revision = self.store.insert_revision(pending_revision)
            provenance_attached = not any(
                item.raw_record_id == raw_record.raw_record_id
                for item in self.store.list_revision_provenance(revision.activity_revision_id)
            )
            self.store.attach_revision_provenance(revision.activity_revision_id, raw_record.raw_record_id)
            previous_current_revision_id = self.store.get_entity(entity.activity_entity_id).current_revision_id
            self.store.set_current_revision(entity.activity_entity_id, revision.activity_revision_id)

        return PersistentIngestionResult(
            entity.activity_entity_id,
            source_link.source_link_id,
            raw_record.raw_record_id,
            revision.activity_revision_id,
            entity_created,
            raw_payload_created,
            raw_record_created,
            revision_created,
            provenance_attached,
            previous_current_revision_id != revision.activity_revision_id,
        )
def _is_missing(getter: Callable[[str], object], identity: str) -> bool:
    try:
        getter(identity)
    except KeyError:
        return True
    return False
