from pathlib import Path
import json

STATE_VOCAB = {'USE','PILOT','NOT_NOW','UNKNOWN','EVIDENCE_INCOMPLETE','OWNER_MISSING','REVIEW_REQUIRED','HOLD','PARTIAL','DISPUTED'}
POSITIVE = {'USE','PILOT'}
AXES = {'KUBERNETES','KAFKA'}
UNRESOLVED = {None,'','UNKNOWN','UNMEASURED','UNRESOLVED','NOT_ASSESSED','INCOMPLETE','EVIDENCE_INCOMPLETE','CONTRADICTORY'}


def _unresolved(value):
    if isinstance(value, (list, dict)):
        return not value
    return value in UNRESOLVED


def _err(errors, rule, message):
    errors.append({'rule': rule, 'message': message})


def _bindings(root):
    raw = json.loads((root / 'fixtures/FIXTURE_BINDINGS.json').read_text(encoding='utf-8'))
    return {item['fixture_id']: json.loads((root / item['path']).read_text(encoding='utf-8')) for item in raw['fixtures']}


def _decisive_keys(axis):
    common = ['problem_class','current_symptom','measured_trigger','trigger_threshold','trigger_state','availability_requirement','current_availability_result','simpler_alternative','simpler_alternative_result','operational_owner','skill_readiness','failure_surface','migration_cost','rollback_path','evidence_ref','authority_refs']
    if axis == 'KAFKA':
        return common + ['event_rate','retention_requirement','replay_requirement','ordering_requirement','consumer_count']
    return common + ['workload_count','team_count','deployments_per_week','scheduler_attributable_slo_breaches_90d']


def _has_unresolved(axis, evidence):
    for key in _decisive_keys(axis):
        value = evidence.get(key)
        if _unresolved(value):
            return True
        if isinstance(value, list) and not value:
            return True
    return False


def _trigger(evidence):
    unresolved = _unresolved(evidence.get('trigger_state')) or _has_unresolved(evidence.get('technology'), evidence)
    present = not unresolved and evidence.get('trigger_state') == 'PRESENT' and str(evidence.get('simpler_alternative_result','')).startswith('MISSES_')
    absent = not unresolved and evidence.get('trigger_state') == 'ABSENT' and evidence.get('simpler_alternative_result') == 'MEETS_DECLARED_REQUIREMENT'
    return unresolved, present, absent


def _positive_ready(axis, evidence):
    shared = all(not _unresolved(evidence.get(k)) for k in ['operational_owner','skill_readiness','failure_surface','migration_cost','rollback_path','evidence_ref'])
    if not shared:
        return False
    if axis == 'KUBERNETES':
        return (
            evidence.get('problem_class') == 'CONTAINER_WORKLOAD_ORCHESTRATION'
            and evidence.get('current_availability_result') == 'MANAGED_HOST_MISSES_PLACEMENT_AND_FAILOVER_REQUIREMENT'
            and isinstance(evidence.get('workload_count'), int) and evidence['workload_count'] >= 20
            and isinstance(evidence.get('team_count'), int) and evidence['team_count'] >= 3
        )
    return (
        evidence.get('problem_class') == 'DURABLE_EVENT_STREAMING'
        and evidence.get('replay_requirement') == 'REGULATORY_REPLAY_REQUIRED'
        and isinstance(evidence.get('consumer_count'), int) and evidence['consumer_count'] >= 4
        and evidence.get('ordering_requirement') == 'PER_ACCOUNT_ORDER_WITH_EXPLICIT_KEY_AND_PARTITION_CONTRACT'
    )


def _authority_ok(axis, refs):
    if not isinstance(refs, list):
        return False
    got = set(refs)
    if axis == 'KUBERNETES':
        required = {'source://AWR-001/K8SOV','lesson://AWR-001#claim/AWR-001-K1','lesson://AWR-001#claim/AWR-001-K2','lesson://AWR-001#claim/AWR-001-K4'}
    else:
        required = {'source://AWR-001/KAFKAINTRO','lesson://AWR-001#claim/AWR-001-K3','lesson://AWR-001#claim/AWR-001-K4'}
    return required <= got


def _token_basis(axis, evidence, tokens):
    for key in _decisive_keys(axis):
        value = evidence.get(key)
        if isinstance(value, str) and any(token in value.upper() for token in tokens):
            return True
    return False


def _typed_state_basis(axis, state, evidence):
    keys = _decisive_keys(axis)
    unresolved_count = sum(_unresolved(evidence.get(key)) for key in keys)
    resolved_count = len(keys) - unresolved_count
    if state in {'UNKNOWN','EVIDENCE_INCOMPLETE'}:
        return unresolved_count > 0
    if state == 'OWNER_MISSING':
        return not evidence.get('operational_owner')
    if state == 'REVIEW_REQUIRED':
        return _token_basis(axis, evidence, {'CONFLICT','CONTRADICT','GLOBAL_ORDER','EXCEEDS_TOLERANCE','STALE'})
    if state == 'HOLD':
        return _token_basis(axis, evidence, {'HOLD','BLOCKED','WAITING','PAUSED'})
    if state == 'PARTIAL':
        return unresolved_count > 0 and resolved_count > 0
    if state == 'DISPUTED':
        return _token_basis(axis, evidence, {'CONFLICT','CONTRADICT','DISPUTED'})
    return True


def validate_submission(submission, root=None):
    root = Path(root or Path(__file__).resolve().parent)
    errors = []
    fixtures = _bindings(root)
    fixture_id = submission.get('fixture_id')
    fixture = fixtures.get(fixture_id)
    if fixture is None:
        _err(errors, 'R3', 'Unknown fixture_id; select a shipped fixture.')
        return {'ok': False, 'errors': errors, 'fixture_id': fixture_id}

    records = submission.get('records')
    if not isinstance(records, list) or len(records) != 2:
        _err(errors, 'R1', 'Exactly two decision records are required.')
        return {'ok': False, 'errors': errors, 'fixture_id': fixture_id}
    axes = [record.get('decision_axis') for record in records]
    if set(axes) != AXES or len(set(axes)) != 2:
        _err(errors, 'R1', 'Decision axes must be exactly KUBERNETES and KAFKA.')
    by_axis = {record.get('decision_axis'): record for record in records}
    fixture_keys = {'KUBERNETES':'kubernetes','KAFKA':'kafka'}

    for axis in AXES:
        if axis not in by_axis:
            continue
        record = by_axis[axis]
        state = record.get('decision_state')
        evidence = record.get('evidence') if isinstance(record.get('evidence'), dict) else {}
        if state not in STATE_VOCAB:
            _err(errors, 'R2', f'{axis}: invalid decision_state.')
        if evidence != fixture['decisions'][fixture_keys[axis]]:
            _err(errors, 'R3', f'{axis}: evidence does not exactly match the selected fixture.')
        for key in ['rationale','review_due','stop_or_pivot_trigger']:
            if not isinstance(record.get(key), str) or not record.get(key, '').strip():
                _err(errors, 'R11', f'{axis}: {key} is required.')
        if not _authority_ok(axis, evidence.get('authority_refs')):
            _err(errors, 'R10', f'{axis}: authority_refs do not contain the required FACT/DESIGN trace.')

        unresolved, trigger_present, trigger_absent = _trigger(evidence)
        if state in POSITIVE:
            rule = 'R4' if axis == 'KUBERNETES' else 'R5'
            if unresolved or not trigger_present or not _positive_ready(axis, evidence):
                _err(errors, rule, f'{axis}: positive state lacks a complete measured trigger or problem fit.')
            if state == 'USE' and evidence.get('adoption_evidence') != 'VALIDATED_PRODUCTION_APPROVAL':
                _err(errors, 'R7', f'{axis}: USE requires validated adoption evidence; shipped fixtures support at most PILOT.')
        if state == 'NOT_NOW' and not trigger_absent:
            _err(errors, 'R8', f'{axis}: NOT_NOW requires complete trigger absence and a sufficient simpler alternative.')
        if unresolved and state in POSITIVE | {'NOT_NOW'}:
            _err(errors, 'R9', f'{axis}: unresolved decisive evidence requires a typed non-success state.')
        if state in {'UNKNOWN','EVIDENCE_INCOMPLETE','OWNER_MISSING','REVIEW_REQUIRED','HOLD','PARTIAL','DISPUTED'} and not _typed_state_basis(axis, state, evidence):
            _err(errors, 'R12', f'{axis}: {state} lacks its visible typed-state basis.')

    return {'ok': not errors,'errors': errors,'fixture_id': fixture_id,'rules_checked':[f'R{i}' for i in range(1,13)],'checker_has_hidden_answer_key':False}
