#!/usr/bin/env bash
# opencode-kit verify-agent-compliance — self-check enforcement for subagents
# Usage: bash src/verify-agent-compliance.sh [--agent <name>] [--json]
# Exit codes: 0=PASS (all checks passed), 1=FAIL (violations found), 2=ERROR (check could not complete)
#
# Checks:
#   1. Contract loaded and valid (contract_stale)
#   2. State is valid for this agent (state_violation)
#   3. Agent did NOT self-declare COMPLETE (self_complete — CRITICAL)
#   4. Checkpoint was saved by this agent (checkpoint_missing)
#   5. Pre-flight gate completed (preflight_skipped)
#
# This script is called by subagents BEFORE they return their result.
# Every write-capable and review agent MUST run this script.
# If FAIL, the agent MUST retry the missing step before returning to orchestrator.

set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=./platform.sh
. "$SCRIPT_DIR/platform.sh"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

AGENT_NAME="${AGENT_NAME:-unknown}"
JSON_OUTPUT=false
CONTRACT_FILE="${CONTRACT_FILE:-.opencode/orchestration/contract.json}"
CHECKPOINT_DIR="${CHECKPOINT_DIR:-.opencode/orchestration/checkpoints}"
MAX_CHECKPOINT_AGE_MINUTES="${MAX_CHECKPOINT_AGE_MINUTES:-10}"

# Parse args
while [ $# -gt 0 ]; do
    case "$1" in
        --agent) shift; AGENT_NAME="${1:-$AGENT_NAME}"; shift ;;
        --json) JSON_OUTPUT=true; shift ;;
        *) shift ;;
    esac
done

# --- Resolve contract ---
if [ ! -f "$CONTRACT_FILE" ]; then
    # Try finding it
    for candidate in \
        ".opencode/orchestration/contract.json" \
        "contract.json"; do
        if [ -f "$candidate" ]; then
            CONTRACT_FILE="$candidate"
            break
        fi
    done
fi

if [ ! -f "$CONTRACT_FILE" ]; then
    if $JSON_OUTPUT; then
        echo '{"pass":false,"violations":[{"id":"contract_missing","severity":"CRITICAL","msg":"contract.json not found — agent must load contract"}],"exit_code":2}'
    else
        echo -e "${RED}⛔ CONTRACT MISSING: contract.json not found${NC}"
        echo -e "   ${BOLD}Fix:${NC} Run pre-flight step 1: lean-ctx ctx_knowledge recall --query \"orchestration-contract\""
    fi
    exit 2
fi

# Require Python for structured checks
if [ -z "$PYTHON_CMD" ]; then
    if $JSON_OUTPUT; then
        echo '{"pass":false,"violations":[{"id":"python_missing","severity":"CRITICAL","msg":"Python3 required for compliance check"}],"exit_code":2}'
    fi
    exit 2
fi

# Get ISO timestamp for recency checks
ISO_NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
EPOCH_NOW=$(date -u +%s)

# --- Run compliance checks via Python ---
CHECK_RESULT=$($PYTHON_CMD -c "
import json, os, sys, glob as gmod
from datetime import datetime, timezone, timedelta

contract_path = '$CONTRACT_FILE'
agent_name = '$AGENT_NAME'
checkpoint_dir = '$CHECKPOINT_DIR'
max_age_minutes = $MAX_CHECKPOINT_AGE_MINUTES
now_epoch = $EPOCH_NOW
now_iso = '$ISO_NOW'

violations = []
checks_ran = 0

# Load contract
try:
    with open(contract_path) as f:
        contract = json.load(f)
except Exception as e:
    violations.append({
        'id': 'contract_stale',
        'severity': 'CRITICAL',
        'check': 'Contract load',
        'msg': f'Cannot parse contract.json: {e}',
        'fix': 'Re-run pre-flight: lean-ctx ctx_knowledge recall --query \"orchestration-contract\"'
    })
    print(json.dumps({'pass': False, 'violations': violations, 'exit_code': 2}))
    sys.exit(0)

# CHECK 1: State validation
state = contract.get('state', 'UNKNOWN')
valid_states = ['INIT', 'PLAN', 'PLAN_SCORED', 'EXECUTE', 'EXECUTE_SCORED', 'REVIEW', 'REVIEW_SCORED', 'COMPLETE', 'BLOCKED']
checks_ran += 1

if state == 'UNKNOWN':
    violations.append({
        'id': 'state_unknown',
        'severity': 'CRITICAL',
        'check': 'State validation',
        'msg': 'Contract state is UNKNOWN',
        'fix': 'Set state to a valid state before proceeding'
    })
elif state not in valid_states:
    violations.append({
        'id': 'state_invalid',
        'severity': 'CRITICAL',
        'check': 'State validation',
        'msg': f'Invalid state: \"{state}\". Valid: {valid_states}',
        'fix': 'Correct state to a valid value'
    })

# CHECK 2: Self-declared COMPLETE (CRITICAL — this is the #1 violation we see)
checks_ran += 1
if agent_name != 'orchestrator' and state == 'COMPLETE':
    violations.append({
        'id': 'self_complete',
        'severity': 'CRITICAL',
        'check': 'No self-COMPLETE',
        'msg': f'Agent \"{agent_name}\" set state=COMPLETE. Only orchestrator may set COMPLETE.',
        'fix': 'Subagents must set EXECUTE_SCORED (build) or REVIEW_SCORED (review), never COMPLETE. Restore state, re-transition, and re-save checkpoint.'
    })

# CHECK 3: Checkpoint was saved by this agent (check recency)
checks_ran += 1
checkpoint_files = sorted(gmod.glob(os.path.join(checkpoint_dir, 'checkpoint-*.json')), reverse=True)
agent_checkpoint_found = False
recent_checkpoint_found = False

for cp_file in checkpoint_files[:20]:  # check last 20
    try:
        with open(cp_file) as f:
            cp = json.load(f)
        cp_agent = cp.get('agent', '')
        cp_ts = cp.get('timestamp', '')
        if cp_agent == agent_name:
            agent_checkpoint_found = True
            # Check recency
            try:
                cp_dt = datetime.fromisoformat(cp_ts.replace('Z', '+00:00'))
                cp_epoch = int(cp_dt.timestamp())
                age_seconds = now_epoch - cp_epoch
                if age_seconds <= (max_age_minutes * 60):
                    recent_checkpoint_found = True
                    break
            except:
                pass
    except:
        continue

if not recent_checkpoint_found:
    severity = 'CRITICAL' if not agent_checkpoint_found else 'HIGH'
    violations.append({
        'id': 'checkpoint_missing',
        'severity': severity,
        'check': 'Checkpoint saved',
        'msg': f'No recent checkpoint from agent \"{agent_name}\" (within {max_age_minutes} min)' if not agent_checkpoint_found else f'Last checkpoint from \"{agent_name}\" is older than {max_age_minutes} minutes',
        'fix': 'Save a checkpoint: bash .opencode/src/checkpoint.sh save --agent {} --step <step> --summary \"<description>\"'.format(agent_name)
    })

# CHECK 4: Pre-flight steps completed (heuristic: contract has recent decisions_log entry from this agent)
checks_ran += 1
gov = contract.get('governance', {})
decisions_log = gov.get('decisions_log', [])
preflight_entries = [d for d in decisions_log if d.get('agent') == agent_name and 'preflight' in str(d.get('action', '')).lower()]
if not preflight_entries and agent_name != 'orchestrator':
    violations.append({
        'id': 'preflight_skipped',
        'severity': 'HIGH',
        'check': 'Pre-flight gate',
        'msg': f'No pre-flight completion recorded for \"{agent_name}\" in decisions_log',
        'fix': 'Log pre-flight completion to decisions_log, or re-run pre-flight steps'
    })

# CHECK 5: State transition legality
checks_ran += 1
# Define legal transitions per agent type
agent_lanes = {
    'explorer': {'allowed_in': ['INIT', 'PLAN', 'EXECUTE', 'REVIEW'], 'transition_to': None, 'readonly': True},
    'librarian': {'allowed_in': ['INIT', 'PLAN', 'EXECUTE', 'REVIEW'], 'transition_to': None, 'readonly': True},
    'observer': {'allowed_in': ['INIT', 'PLAN', 'EXECUTE', 'EXECUTE_SCORED', 'REVIEW', 'REVIEW_SCORED'], 'transition_to': None, 'readonly': True},
    'architect': {'allowed_in': ['INIT', 'PLAN'], 'transition_to': None, 'readonly': True},
    'planner': {'allowed_in': ['INIT', 'PLAN'], 'transition_to': 'PLAN_SCORED', 'readonly': False},
    'fixer': {'allowed_in': ['EXECUTE'], 'transition_to': 'EXECUTE_SCORED', 'readonly': False},
    'designer': {'allowed_in': ['EXECUTE'], 'transition_to': 'EXECUTE_SCORED', 'readonly': False},
    'task_manager': {'allowed_in': ['EXECUTE'], 'transition_to': 'EXECUTE_SCORED', 'readonly': False},
    'documentation_agent': {'allowed_in': ['EXECUTE'], 'transition_to': 'EXECUTE_SCORED', 'readonly': False},
    'database_specialist': {'allowed_in': ['EXECUTE'], 'transition_to': 'EXECUTE_SCORED', 'readonly': False},
    'devops_agent': {'allowed_in': ['EXECUTE'], 'transition_to': 'EXECUTE_SCORED', 'readonly': False},
    'oracle': {'allowed_in': ['REVIEW'], 'transition_to': 'REVIEW_SCORED', 'readonly': False},
    'code_reviewer': {'allowed_in': ['REVIEW'], 'transition_to': 'REVIEW_SCORED', 'readonly': False},
    'security_reviewer': {'allowed_in': ['REVIEW'], 'transition_to': 'REVIEW_SCORED', 'readonly': False},
    'testing_specialist': {'allowed_in': ['REVIEW'], 'transition_to': 'REVIEW_SCORED', 'readonly': False},
    'council': {'allowed_in': ['PLAN', 'REVIEW'], 'transition_to': None, 'readonly': True},
    'learner': {'allowed_in': ['COMPLETE'], 'transition_to': None, 'readonly': True},
}

# Normalize agent name (underscores and hyphens are equivalent)
norm_agent = agent_name.lower().replace('-', '_')
lane_info = None
for lane_key, info in agent_lanes.items():
    if norm_agent == lane_key:
        lane_info = info
        break

if lane_info:
    if state not in lane_info['allowed_in'] and state not in ['COMPLETE', 'BLOCKED']:
        violations.append({
            'id': 'state_unauthorized',
            'severity': 'CRITICAL',
            'check': 'State authorization',
            'msg': f'Agent \"{agent_name}\" is in state \"{state}\" but is only allowed in: {lane_info[\"allowed_in\"]}',
            'fix': f'Restore checkpoint or set state to one of: {lane_info[\"allowed_in\"]}'
        })

# CHECK 6: Contract was actually loaded (heuristic: contract has non-empty session fields)
checks_ran += 1
session = contract.get('session', {})
if not session or not session.get('task_id'):
    violations.append({
        'id': 'contract_not_loaded',
        'severity': 'CRITICAL',
        'check': 'Contract loaded',
        'msg': 'Contract appears uninitialized — session.task_id is missing',
        'fix': 'Load contract: lean-ctx ctx_knowledge recall --query \"orchestration-contract\"'
    })

# Result
passed = len(violations) == 0
exit_code = 0 if passed else 1

# Sort violations by severity
sev_order = {'CRITICAL': 0, 'HIGH': 1, 'MEDIUM': 2, 'LOW': 3}
violations.sort(key=lambda v: sev_order.get(v.get('severity', 'LOW'), 99))

result = {
    'pass': passed,
    'agent': agent_name,
    'contract_state': state,
    'checks_ran': checks_ran,
    'violations': violations,
    'exit_code': exit_code,
    'timestamp': now_iso
}
print(json.dumps(result, indent=2))
" 2>/dev/null || echo '{"pass":false,"violations":[{"id":"check_error","severity":"CRITICAL","msg":"Compliance check script failed to execute"}],"exit_code":2}')

# --- Parse result ---
PASSED=false
EXIT_CODE=2
if [ -n "$PYTHON_CMD" ]; then
    PASSED=$($PYTHON_CMD -c "
import json
r = json.loads('''$CHECK_RESULT''')
print('true' if r.get('pass') else 'false')
" 2>/dev/null || echo "false")
    EXIT_CODE=$($PYTHON_CMD -c "
import json
r = json.loads('''$CHECK_RESULT''')
print(int(r.get('exit_code', 2)))
" 2>/dev/null || echo "2")
fi

if $JSON_OUTPUT; then
    echo "$CHECK_RESULT"
else
    if [ "$PASSED" = "true" ]; then
        echo -e "${GREEN}✅ Agent Compliance: PASS${NC}"
        echo -e "   ${BOLD}Agent:${NC} $AGENT_NAME"
        echo -e "   ${BOLD}State:${NC} $($PYTHON_CMD -c "import json; r=json.loads('''$CHECK_RESULT'''); print(r.get('contract_state','?'))" 2>/dev/null || echo "?")"
        echo -e "   ${BOLD}Checks:${NC} $($PYTHON_CMD -c "import json; r=json.loads('''$CHECK_RESULT'''); print(r.get('checks_ran',0))" 2>/dev/null || echo "0") passed"
    else
        echo -e "${RED}⛔ Agent Compliance: FAIL${NC}"
        echo -e "   ${BOLD}Agent:${NC} $AGENT_NAME"
        echo -e "   ${BOLD}Violations:${NC}"
        if [ -n "$PYTHON_CMD" ]; then
            $PYTHON_CMD -c "
import json
r = json.loads('''$CHECK_RESULT''')
for v in r.get('violations', []):
    sev = v.get('severity', '?')
    cid = v.get('id', '?')
    msg = v.get('msg', '')
    fix = v.get('fix', '')
    sev_color = '31'  # red
    if sev == 'HIGH':
        sev_color = '33'  # yellow
    print(f'     [\033[1;{sev_color}m{sev}\033[0m] {cid}')
    print(f'       → {msg}')
    if fix:
        print(f'       \033[0;36mFix:\033[0m {fix}')
" 2>/dev/null || true
        fi
        echo
        echo -e "   ${YELLOW}⚠️  Self-check FAILED — retry missing steps before returning to orchestrator${NC}"
    fi
fi

exit "$EXIT_CODE"
