from __future__ import annotations

from uuid import uuid4

import pytest
from src.domain.capabilities.lifecycle import CapabilityState, PlanAction
from src.domain.capabilities.native_adapters import (
    FilesProvisioner,
    NotificationsProvisioner,
    RecordsProvisioner,
    SecretsProvisioner,
    WebhooksProvisioner,
)


class _Result:
    def __init__(self, rows):
        self._rows = rows

    def all(self):
        return self._rows


class _DB:
    def __init__(self, pack_rows, catalog_rows):
        self._pack_rows = pack_rows
        self._catalog_rows = catalog_rows
        self._step = 0

    async def execute(self, _query):
        if not self._pack_rows:
            return _Result([])
        result = self._pack_rows if self._step % 2 == 0 else self._catalog_rows
        self._step += 1
        return _Result(result)


def _db(
    provides,
    *,
    status="installed",
    blocker=None,
    entities=None,
    events=None,
    webhooks=None,
    bindings=None,
):
    slug = f"{provides.replace('.', '-')}-pack"
    composition = {
        "schema_version": 2,
        "provides": provides if isinstance(provides, list) else [provides],
        "entities": entities or [],
        "events": events or [],
        "webhook_bindings": webhooks or [],
        "provider_bindings": bindings or [],
    }
    return _DB(
        [(slug, status, blocker)],
        [(slug, composition)],
    )


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("adapter", "provided", "port"),
    [
        (FilesProvisioner, "files.attachments", "storage.list"),
        (RecordsProvisioner, "data.entity", "records.query"),
    ],
)
async def test_native_data_adapters_plan_status_runtime_and_reconcile(adapter, provided, port):
    db = _db(provided)
    instance = adapter(db)
    project_id = uuid4()

    declared = await instance.declare(project_id, {"enabled": True})
    assert declared["capability"] == instance.capability
    plan = await instance.plan(project_id)
    assert plan.action == PlanAction.RECONCILE
    status = await instance.status(project_id)
    assert status.state == CapabilityState.READY
    requirements = await instance.runtime_requirements(project_id)
    assert port in {item.port_id for item in requirements.ports}
    result = await instance.reconcile(project_id)
    assert result.state == CapabilityState.READY
    assert await instance.status(project_id) == status


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ("adapter", "provided"),
    [
        (NotificationsProvisioner, "notification.email"),
        (WebhooksProvisioner, "webhook.subscription"),
        (SecretsProvisioner, "secrets.project"),
    ],
)
async def test_native_control_adapters_are_hosting_neutral_and_non_secret(adapter, provided):
    instance = adapter(_db(provided))
    status = await instance.status(uuid4())
    requirements = await instance.runtime_requirements(uuid4())
    assert status.state == CapabilityState.READY
    assert requirements.ports == ()
    assert all(
        "secret" not in str(requirements.to_dict()).lower()
        or "p2hs-secrets" in str(requirements.to_dict())
        for _ in [0]
    )
    assert "provider" not in str(status.details).lower()


@pytest.mark.asyncio
async def test_native_adapter_missing_pack_is_declared_without_fabricating_runtime():
    instance = FilesProvisioner(_DB([], []))
    project_id = uuid4()
    plan = await instance.plan(project_id)
    status = await instance.status(project_id)
    requirements = await instance.runtime_requirements(project_id)
    assert plan.action == PlanAction.NOOP
    assert status.state == CapabilityState.DECLARED
    assert requirements.ports == ()


@pytest.mark.asyncio
async def test_native_adapter_blocked_pack_is_repairable_and_idempotent():
    instance = RecordsProvisioner(
        _db("data.entity", status="blocked", blocker={"reason": "schema"})
    )
    project_id = uuid4()
    plan = await instance.plan(project_id)
    status = await instance.status(project_id)
    result = await instance.reconcile(project_id)
    assert plan.action == PlanAction.BLOCKED
    assert status.state == CapabilityState.BLOCKED
    assert status.issues[0].repairable is True
    assert result.state == CapabilityState.BLOCKED
