"""Phase 11D8B: ownership contract validation on effective composition."""
from __future__ import annotations

from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4

import pytest
from src.domain.app_blocks.compiler import compile_install_plan
from src.domain.app_blocks.registry import load_default_registry
from src.domain.feature_packs.composer import derive_pack_composition
from src.domain.feature_packs.normalize import stamp_composition
from src.domain.feature_packs.ownership import (
    INVALID_OWNERSHIP_CONTRACT,
    OwnershipContractError,
    validate_composition_ownership_contracts,
    validate_entity_ownership_contract,
)
from src.domain.feature_packs.requirements_spec import CompositionRequirement
from src.domain.feature_packs.service import PackInstallService

from tests.test_phase11_pack_migration_materialization import _captured_composition

_OWNER_POLICY = {
    "version": 1,
    "roles": {
        "member": {
            "create": {"effect": "allow", "scope": "owner"},
            "read": {"effect": "allow", "scope": "owner"},
            "update": {"effect": "allow", "scope": "owner"},
            "delete": {"effect": "allow", "scope": "owner"},
        },
        "admin": {
            "create": {"effect": "allow", "scope": "all"},
            "read": {"effect": "allow", "scope": "all"},
            "update": {"effect": "allow", "scope": "all"},
            "delete": {"effect": "allow", "scope": "all"},
        },
    },
}


def _entity(
    *,
    key="ownership_fixture",
    rls=False,
    owner_managed=False,
    policy=_OWNER_POLICY,
    settings=None,
):
    body = {
        "entity_key": key,
        "name": key,
        "plural_name": key,
        "rls_enabled": rls,
        "owner_managed": owner_managed,
        "schema": {
            "fields": [{"key": "title", "name": "Title", "type": "text"}],
            "access_policy": policy,
        },
    }
    if settings is not None:
        body["settings"] = settings
    return body


def _composition(entity):
    return {"function_slugs": [], "entities": [entity], "requires": []}


def test_invalid_member_create_owner_access_without_stamp_rejected():
    entity = _entity(key="ownership_bad_fixture", rls=False, owner_managed=False)
    with pytest.raises(OwnershipContractError) as caught:
        validate_entity_ownership_contract(entity)
    err = caught.value
    assert err.code == INVALID_OWNERSHIP_CONTRACT
    assert err.entity_key == "ownership_bad_fixture"
    assert err.role == "member"
    assert err.actions == ("read", "update", "delete")
    assert "rls_enabled=false" in str(err)
    assert "owner_managed=false" in str(err)
    assert "ownership_bad_fixture" in str(err)


def test_omitted_later_scope_defaults_to_owner_like_runtime():
    policy = {
        "version": 1,
        "roles": {
            "member": {
                "create": {"effect": "allow"},
                "read": {"effect": "allow"},
            }
        },
    }
    with pytest.raises(OwnershipContractError) as caught:
        validate_entity_ownership_contract(_entity(policy=policy))
    assert caught.value.actions == ("read",)


def test_rls_enabled_creator_owned_accepted():
    validate_entity_ownership_contract(_entity(rls=True))


def test_owner_managed_creator_owned_accepted():
    validate_entity_ownership_contract(_entity(owner_managed=True))


def test_settings_rls_flag_is_effective():
    entity = _entity(rls=False)
    entity["rls_enabled"] = False
    entity["settings"] = {"rls_enabled": True}
    validate_entity_ownership_contract(entity)


def test_member_create_with_read_all_accepted():
    policy = {
        "version": 1,
        "roles": {
            "member": {
                "create": {"effect": "allow", "scope": "all"},
                "read": {"effect": "allow", "scope": "all"},
            }
        },
    }
    validate_entity_ownership_contract(_entity(policy=policy))


def test_member_create_only_without_later_owner_access_accepted():
    policy = {
        "version": 1,
        "roles": {
            "member": {"create": {"effect": "allow", "scope": "owner"}},
            "admin": {"read": {"effect": "allow", "scope": "all"}},
        },
    }
    validate_entity_ownership_contract(_entity(policy=policy))


def test_assignment_owned_member_read_without_member_create_accepted():
    policy = {
        "version": 1,
        "roles": {
            "member": {
                "read": {"effect": "allow", "scope": "owner"},
                "update": {"effect": "allow", "scope": "owner"},
            },
            "admin": {
                "create": {"effect": "allow", "scope": "all"},
                "read": {"effect": "allow", "scope": "all"},
            },
        },
    }
    validate_entity_ownership_contract(_entity(policy=policy))


def test_no_access_policy_is_not_an_owner_contract():
    entity = _entity()
    entity["schema"].pop("access_policy")
    validate_entity_ownership_contract(entity)


def test_stamp_rejects_invalid_and_accepts_valid():
    with pytest.raises(OwnershipContractError):
        stamp_composition("local-test-ownership-bad", _composition(_entity()))
    stamped = stamp_composition(
        "local-test-ownership-rls", _composition(_entity(rls=True))
    )
    assert stamped["entities"][0]["rls_enabled"] is True


def test_catalog_owner_scoped_packs_still_stamp():
    documents = _captured_composition(
        "alembic/versions/142_documents_lifecycle_pack.py", "documents-lifecycle"
    )
    notifications = _captured_composition(
        "alembic/versions/140_notifications_in_app_pack.py", "notifications-in-app"
    )
    attachments = _captured_composition(
        "alembic/versions/141_file_attachments_pack.py", "file-attachments"
    )
    validate_composition_ownership_contracts(documents)
    validate_composition_ownership_contracts(notifications)
    validate_composition_ownership_contracts(attachments)
    assert documents["entities"][0]["rls_enabled"] is True
    member = notifications["entities"][0]["schema"]["access_policy"]["roles"]["member"]
    assert "create" not in member
    attach_member = attachments["entities"][0]["schema"]["access_policy"]["roles"]["member"]
    assert attach_member["read"]["scope"] == "all"


def test_generated_member_compose_still_couples_rls():
    req = CompositionRequirement.model_validate(
        {
            "name": "intake_note",
            "actors": ["member", "admin"],
            "requirements": [
                {"action": "create", "resource": "intake_note", "actor": "member"},
                {"action": "read", "resource": "intake_note", "actor": "member"},
                {"action": "read", "resource": "intake_note", "actor": "admin"},
            ],
            "fields": [{"key": "title", "label": "Title", "type": "text", "required": True}],
        }
    )
    spec = derive_pack_composition(req, "p11d8b")
    assert spec.entities[0].rls_enabled is True
    assert spec.access_policy.roles["member"]["read"].scope == "owner"


def test_generated_non_owner_compose_accepted():
    req = CompositionRequirement.model_validate(
        {
            "name": "quote_request",
            "actors": ["anonymous", "admin"],
            "requirements": [
                {"action": "create", "resource": "quote_request", "actor": "anonymous"},
                {"action": "read", "resource": "quote_request", "actor": "admin"},
            ],
            "fields": [{"key": "name", "label": "Name", "type": "text", "required": True}],
        }
    )
    spec = derive_pack_composition(req, "p11d8b")
    assert spec.entities[0].rls_enabled is False


def test_safe_to_contradictory_update_rejected():
    validate_composition_ownership_contracts(_composition(_entity(policy={
        "version": 1,
        "roles": {
            "member": {
                "create": {"effect": "allow", "scope": "all"},
                "read": {"effect": "allow", "scope": "all"},
            }
        },
    })))
    with pytest.raises(OwnershipContractError):
        validate_composition_ownership_contracts(
            _composition(
                _entity(
                    policy={
                        "version": 1,
                        "roles": {
                            "member": {
                                "create": {"effect": "allow", "scope": "all"},
                                "read": {"effect": "allow", "scope": "owner"},
                            }
                        },
                    }
                )
            )
        )


async def _install(composition):
    pack = SimpleNamespace(
        id=uuid4(),
        slug="ownership-pack",
        status="active",
        version="1.0.0",
        composition=composition,
        input_schema=None,
    )
    cloud = AsyncMock()
    svc = PackInstallService(AsyncMock(), MagicMock(), cloud)
    svc._get_pack_by_slug = AsyncMock(return_value=pack)
    svc._installed_config = AsyncMock(return_value={})
    svc._missing_prerequisites = AsyncMock(return_value=([], []))
    result = await svc.install_pack(
        project_id=uuid4(), pack_slug="ownership-pack", config={}
    )
    return result, cloud


@pytest.mark.asyncio
async def test_install_rejects_invalid_before_cloud_create():
    result, cloud = await _install(_composition(_entity(key="ownership_bad_fixture")))
    assert result["ok"] is False
    assert result["status"] == "blocked"
    assert result["blocker"]["validation_code"] == INVALID_OWNERSHIP_CONTRACT
    assert result["blocker"]["entity_key"] == "ownership_bad_fixture"
    assert result["blocker"]["role"] == "member"
    cloud.create_project_entity.assert_not_called()
    cloud.patch_project_entity.assert_not_called()


@pytest.mark.asyncio
async def test_install_update_to_contradictory_schema_rejected():
    result, cloud = await _install(
        _composition(
            _entity(
                policy={
                    "version": 1,
                    "roles": {
                        "member": {
                            "create": {"effect": "allow", "scope": "all"},
                            "read": {"effect": "allow", "scope": "owner"},
                        }
                    },
                }
            )
        )
    )
    assert result["ok"] is False
    assert result["blocker"]["validation_code"] == INVALID_OWNERSHIP_CONTRACT
    cloud.create_project_entity.assert_not_called()


def test_validator_is_not_documents_specific():
    repo = Path(__file__).resolve().parents[1]
    src = (repo / "src/domain/feature_packs/ownership.py").read_text(encoding="utf-8")
    service = (repo / "src/domain/feature_packs/service.py").read_text(encoding="utf-8")
    assert "document" not in src.lower()
    assert "documents-lifecycle" not in src
    assert "documents-lifecycle" not in service
    assert "validate_composition_ownership_contracts" in service


def test_app_block_plan_still_resolves_catalog_pack():
    registry = load_default_registry()
    with patch(
        "src.domain.app_blocks.compiler.select_capabilities",
        lambda _intent: [("documents", "documents", "Selected.")],
    ):
        plan = compile_install_plan(None, registry=registry)
    assert not plan.unsatisfiable
    assert "documents-lifecycle" in plan.required_feature_packs
