#!/usr/bin/env python3
"""Static validation usable before PHP/MySQL are available."""

from pathlib import Path
import json
import re
import sys

ROOT = Path(__file__).resolve().parents[1]
MIGRATIONS = ROOT / "app" / "Database" / "Migrations"

required_files = [
    "composer.json", ".env.example", "README.md", "INSTALL_TRILOK.md",
    "app/Services/MahJonggAssignmentService.php",
    "app/Database/Migrations/2026-08-12-000007_CreateWorkspaceProfilesAndMjOperations.php",
    "app/Domain/Scoring/ResponseValue.php",
    "docs/OMR_SERVICE_CONTRACT_EXAMPLE.json",
]
for name in required_files:
    if not (ROOT / name).is_file():
        raise SystemExit(f"FAIL: missing {name}")

composer = json.loads((ROOT / "composer.json").read_text())
assert composer["require"]["php"] == "^8.2"
assert "codeigniter4/framework" in composer["require"]
assert "codeigniter4/shield" in composer["require"]
json.loads((ROOT / "docs/OMR_SERVICE_CONTRACT_EXAMPLE.json").read_text())

php = "\n".join(path.read_text() for path in sorted(MIGRATIONS.glob("*.php")))
tables = re.findall(r"CREATE TABLE\s+([a-z0-9_]+)\s*\(", php, flags=re.I)
if len(tables) != 53 or len(tables) != len(set(tables)):
    raise SystemExit(f"FAIL: expected 53 unique application tables; found {len(tables)} / {len(set(tables))} unique")

required_tables = {
    "organizations", "competitions", "b360_user_profiles", "organization_users",
    "organization_user_roles", "user_assignments", "form_structures",
    "form_contents", "participations", "event_entries", "import_batches",
    "submissions", "submission_responses", "processing_attempts", "results",
    "result_revisions", "advancement_selections", "audit_events",
    "mj_round_assignments", "workspace_profiles", "report_definitions",
    "report_collections", "competition_workspace_profiles",
    "organization_primary_admins", "mj_assignment_versions",
    "scoresheet_generations",
}
missing = sorted(required_tables - set(tables))
if missing:
    raise SystemExit(f"FAIL: required tables absent: {', '.join(missing)}")

invariants = {
    "utf8mb4": php.count("DEFAULT CHARSET=utf8mb4") >= 53,
    "idempotent imports": "uq_import_idempotency" in php,
    "idempotent submissions": "uq_submission_idempotency" in php,
    "zero/unanswered state": "response_state ENUM('unanswered','answered','ambiguous','invalid')" in php,
    "manual advancement": "manual_exception" in php,
    "real/effective audit identity": "authenticated_user_id" in php and "effective_user_id" in php,
    "no recoverable password": "decode_password" not in php.lower(),
    "no legacy score pipes": "pipe-delimited" not in php.lower(),
    "multi-organization memberships": "uq_organization_user_membership" in php,
    "multiple roles per membership": "uq_organization_membership_role" in php,
    "pending setup lifecycle": "pending_setup" in php and "setup_completed_at" in php,
    "protected primary admin": "organization_primary_admins" in php,
    "versioned workspace reports": "competition_workspace_profiles" in php and "report_collection_items" in php,
    "versioned MJ assignments": "mj_assignment_versions" in php and "input_checksum" in php,
    "versioned score-sheet generations": "scoresheet_generations" in php and "full_page_count" in php,
}
failed = [name for name, ok in invariants.items() if not ok]
if failed:
    raise SystemExit("FAIL: " + "; ".join(failed))

print(f"PASS: {len(tables)} unique MySQL application tables")
print("PASS: JSON contract and Composer definition parse")
print("PASS: core schema invariants")
print("PASS: newB360 M1 foundation structure and schema invariants")
