"""Synthetic lexical retrieval with trusted caller fixtures and live access checks."""
import json
import re
from copy import deepcopy
from dataclasses import dataclass


@dataclass(frozen=True)
class Principal:
    tenant: str
    user: str


class Retrieval:
    def __init__(self):
        self.documents = {
            'a-policy': {'tenant': 'alpha', 'readers': {'alice', 'bob'}, 'version': 1, 'text': 'Current travel policy'},
            'a-finance': {'tenant': 'alpha', 'readers': {'alice'}, 'version': 1, 'text': 'Confidential revenue forecast'},
            'a-support': {'tenant': 'alpha', 'readers': {'bob'}, 'version': 1, 'text': 'Support response procedure'},
            'a-architecture': {'tenant': 'alpha', 'readers': {'alice'}, 'version': 1, 'text': 'Platform architecture decision'},
            'b-finance': {'tenant': 'beta', 'readers': {'carol'}, 'version': 1, 'text': 'Beta revenue forecast'},
        }
        self.cache = {}

    def can_read(self, principal, document):
        return document['tenant'] == principal.tenant and principal.user in document['readers']

    def search(self, principal, query):
        tokens = set(re.findall(r'\w+', query.lower()))
        ranked = []
        for identifier, document in self.documents.items():
            # Check before ranking or returning content, not after generation.
            if not self.can_read(principal, document):
                continue
            score = len(tokens & set(re.findall(r'\w+', document['text'].lower())))
            if score:
                ranked.append((-score, identifier))
        return [identifier for _, identifier in sorted(ranked)[:3]]

    def citation(self, principal, identifier):
        document = self.documents.get(identifier)
        if document is None or not self.can_read(principal, document):
            raise PermissionError('Source unavailable')
        return document['text']

    def answer(self, principal, query):
        key = (principal.tenant, principal.user, query)
        cached = self.cache.get(key)
        if cached:
            valid = all(identifier in self.documents and
                        self.can_read(principal, self.documents[identifier]) and
                        self.documents[identifier]['version'] == version
                        for identifier, version in cached['dependencies'].items())
            # Do not cache empty answers: new evidence should be discoverable.
            if valid:
                return dict(deepcopy(cached['result']), cached=True)
            del self.cache[key]
        identifiers = self.search(principal, query)
        result = {'sources': identifiers, 'text': '\n'.join(self.citation(principal, i) for i in identifiers) if identifiers else 'No accessible evidence'}
        if identifiers:
            # A caller may edit its response; it must not mutate cached evidence.
            self.cache[key] = {'dependencies': {i: self.documents[i]['version'] for i in identifiers}, 'result': deepcopy(result)}
        return dict(result, cached=False)

    def revoke(self, identifier, user):
        self.documents[identifier]['readers'].discard(user)
        self.documents[identifier]['version'] += 1


def scenario():
    store = Retrieval()
    alice = Principal('alpha', 'alice')
    before = store.answer(alice, 'forecast')
    reuse = store.answer(alice, 'forecast')
    bob = store.answer(Principal('alpha', 'bob'), 'forecast')
    beta = store.answer(Principal('beta', 'carol'), 'forecast')
    store.revoke('a-finance', 'alice')
    after = store.answer(alice, 'forecast')
    return {'fixture': 'synthetic five-document lexical corpus; no model', 'alice_before_revocation': before,
            'alice_cached_read': reuse, 'bob_result': bob, 'beta_result': beta, 'alice_after_revocation': after}


if __name__ == '__main__':
    print(json.dumps(scenario(), indent=2, sort_keys=True))
