from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable, Mapping

import jsonschema


@dataclass
class SampleData:
    """Container for connector sample payloads."""

    source_events: Iterable[Mapping[str, Any]]
    normalized_entities: Iterable[Mapping[str, Any]]
    promotion_evidence: Iterable[Mapping[str, Any]]


def _load_schema(name: str) -> Mapping[str, Any]:
    schema_path = Path(__file__).with_name("schemas") / name
    with schema_path.open("r", encoding="utf-8") as handle:
        return json.load(handle)


def _assert_idempotent_source_event(event: Mapping[str, Any]) -> None:
    payload = event.get("raw_payload")
    if payload is None:
        raise AssertionError("source_event.raw_payload missing.")
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest()
    if event.get("sha256") != digest:
        raise AssertionError("source_event.sha256 must equal sha256(sorted raw_payload)).")


class ConnectorTestHarness:
    """Every connector MUST pass these before deployment."""

    _SOURCE_EVENT_SCHEMA = _load_schema("source_event.schema.json")
    _NORMALIZED_ENTITY_SCHEMA = _load_schema("normalized_entity.schema.json")
    _PROMOTION_EVIDENCE_SCHEMA = _load_schema("promotion_evidence.schema.json")

    def test_schema_compliance(self, connector: Any, sample_data: SampleData) -> None:
        """Validate sample outputs against strict JSON Schemas."""
        for record in sample_data.source_events:
            jsonschema.validate(record, self._SOURCE_EVENT_SCHEMA)
            _assert_idempotent_source_event(record)
        for record in sample_data.normalized_entities:
            jsonschema.validate(record, self._NORMALIZED_ENTITY_SCHEMA)
        for record in sample_data.promotion_evidence:
            jsonschema.validate(record, self._PROMOTION_EVIDENCE_SCHEMA)

    def test_idempotency(self, connector: Any, raw_payload: Mapping[str, Any]) -> None:
        """Ensure same input produces identical normalized/evidence output."""
        first = connector.process(item=raw_payload)
        second = connector.process(item=raw_payload)
        if first != second:
            raise AssertionError("Connector output differs across identical inputs.")

    def test_error_handling(self, connector: Any) -> None:
        """Check network/format failures raise connector-specific retriable errors."""
        try:
            connector.process(item=None)  # type: ignore[arg-type]
        except connector.RetriableError:
            return
        except Exception as exc:  # pragma: no cover - defensive guard
            raise AssertionError(f"Connector raised unexpected error type: {exc}") from exc
        raise AssertionError("Connector did not raise RetriableError on malformed input.")

    def test_checkpoint_resume(self, connector: Any) -> None:
        """Verify connector can resume from batch checkpoints after interruption."""
        batch_id = "test-batch"
        first_batch = connector.fetch(batch_id=batch_id, resume=False)
        resumed_batch = connector.fetch(batch_id=batch_id, resume=True)
        first_items = list(first_batch.items)
        resumed_items = list(resumed_batch.items)
        if first_items != resumed_items or first_batch.next_token != resumed_batch.next_token:
            raise AssertionError("Connector fetch is not checkpoint-safe.")
