from __future__ import annotations

import unittest

from govoplan_core.core.datasources import (
    CAPABILITY_DATASOURCE_CATALOGUE,
    CAPABILITY_DATASOURCE_LIFECYCLE,
    CAPABILITY_DATASOURCE_ORIGINS,
    CAPABILITY_DATASOURCE_PUBLICATION,
    DatasourceCatalogueProvider,
    DatasourceDescriptor,
    DatasourceField,
    DatasourceLifecycleProvider,
    DatasourceMaterialization,
    DatasourceOrigin,
    DatasourceOriginProvider,
    DatasourceOriginReadResult,
    DatasourcePublicationProvider,
    DatasourcePublicationResult,
    DatasourceReadResult,
    DatasourceStage,
    datasource_catalogue,
    datasource_lifecycle,
    datasource_origins,
    datasource_publication,
)
from govoplan_core.core.modules import ModuleContext, ModuleManifest
from govoplan_core.core.registry import PlatformRegistry


class _Provider:
    descriptor = DatasourceDescriptor(
        ref="datasource:source-1",
        source_name="monthly_cases",
        name="Monthly cases",
        kind="upload",
        mode="static",
        shape="tabular",
        schema=(DatasourceField(name="case_id", data_type="string", nullable=False),),
        fingerprint="abc123",
    )

    def list_datasources(self, session, principal, *, query="", limit=100):
        del session, principal, query
        return (self.descriptor,)[:limit]

    def get_datasource(self, session, principal, *, datasource_ref):
        del session, principal
        return self.descriptor if datasource_ref == self.descriptor.ref else None

    def read_datasource(self, session, principal, *, request):
        del session, principal, request
        return DatasourceReadResult(
            datasource=self.descriptor,
            rows=({"case_id": "0012"},),
            total_rows=1,
            truncated=False,
        )

    def list_materializations(self, session, principal, *, datasource_ref):
        del session, principal, datasource_ref
        return ()

    def list_stages(self, session, principal, *, limit=100):
        del session, principal, limit
        return ()

    def create_stage(self, session, principal, *, stage):
        del session, principal, stage
        return DatasourceStage(
            ref="stage:1",
            name="Stage",
            source_name="stage",
            kind="upload",
            mode="static",
            shape="tabular",
            state="ready",
        )

    def promote_stage(self, session, principal, *, stage_ref, freeze=False, frozen_label=None):
        del session, principal, stage_ref, freeze, frozen_label
        return self.descriptor, DatasourceMaterialization(
            ref="materialization:1",
            datasource_ref=self.descriptor.ref,
            revision=1,
            state="published",
            fingerprint=self.descriptor.fingerprint,
        )

    def register_origin(self, session, principal, **kwargs):
        del session, principal, kwargs
        return self.descriptor

    def refresh_datasource(self, session, principal, *, datasource_ref):
        del session, principal, datasource_ref
        return self.promote_stage(object(), object(), stage_ref="stage:1")

    def freeze_datasource(self, session, principal, *, datasource_ref, label=None):
        del session, principal, datasource_ref, label
        return self.promote_stage(object(), object(), stage_ref="stage:1")[1]

    def retire_datasource(self, session, principal, *, datasource_ref):
        del session, principal, datasource_ref
        return self.descriptor

    def publish_rows(self, session, principal, *, request):
        del session, principal, request
        return DatasourcePublicationResult(
            ref="publication:1",
            status="published",
            datasource=self.descriptor,
            materialization=DatasourceMaterialization(
                ref="materialization:1",
                datasource_ref=self.descriptor.ref,
                revision=1,
                state="published",
                fingerprint=self.descriptor.fingerprint,
            ),
        )

    def list_origins(self, session, principal, *, query="", limit=100):
        del session, principal, query
        return (
            DatasourceOrigin(
                ref="origin:1",
                source_name="origin",
                name="Origin",
                kind="database",
                shape="tabular",
                supported_modes=("live", "cached"),
                provider="test",
            ),
        )[:limit]

    def get_origin(self, session, principal, *, origin_ref):
        return self.list_origins(session, principal)[0] if origin_ref == "origin:1" else None

    def read_origin(self, session, principal, *, request):
        origin = self.get_origin(session, principal, origin_ref=request.origin_ref)
        return DatasourceOriginReadResult(
            origin=origin,
            rows=({"id": 1},),
            total_rows=1,
            truncated=False,
        )


class DatasourceContractTests(unittest.TestCase):
    def test_capabilities_are_runtime_checkable_and_resolved_without_modules(self) -> None:
        provider = _Provider()
        self.assertIsInstance(provider, DatasourceCatalogueProvider)
        self.assertIsInstance(provider, DatasourceLifecycleProvider)
        self.assertIsInstance(provider, DatasourcePublicationProvider)
        self.assertIsInstance(provider, DatasourceOriginProvider)
        registry = PlatformRegistry()
        registry.register(
            ModuleManifest(
                id="datasource_contract_test",
                name="Datasource contract test",
                version="test",
                capability_factories={
                    CAPABILITY_DATASOURCE_CATALOGUE: lambda context: provider,
                    CAPABILITY_DATASOURCE_LIFECYCLE: lambda context: provider,
                    CAPABILITY_DATASOURCE_PUBLICATION: lambda context: provider,
                    CAPABILITY_DATASOURCE_ORIGINS: lambda context: provider,
                },
            )
        )
        registry.configure_capability_context(ModuleContext(registry=registry, settings=object()))

        self.assertIs(provider, datasource_catalogue(registry))
        self.assertIs(provider, datasource_lifecycle(registry))
        self.assertIs(provider, datasource_publication(registry))
        self.assertIs(provider, datasource_origins(registry))
        self.assertIsNone(datasource_catalogue(PlatformRegistry()))

    def test_descriptor_distinguishes_mode_kind_shape_and_materialization(self) -> None:
        descriptor = _Provider.descriptor

        self.assertEqual("static", descriptor.mode)
        self.assertEqual("upload", descriptor.kind)
        self.assertEqual("tabular", descriptor.shape)


if __name__ == "__main__":
    unittest.main()
