#!/usr/bin/env python3
"""Validate CARBON's OpenAI, Agent Plugins, Cursor, Claude, and skill contracts."""

from __future__ import annotations

import json
import re
from pathlib import Path


ROOT = Path(__file__).resolve().parent.parent
VERSION = "1.32.20"
PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json"
PORTABLE_FIELDS = {
    "$schema",
    "name",
    "version",
    "description",
    "author",
    "homepage",
    "repository",
    "license",
    "keywords",
    "extensions",
}
SKILL_FIELDS = {
    "name",
    "description",
    "license",
    "compatibility",
    "metadata",
    "allowed-tools",
}
NAME_PATTERN = re.compile(r"^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$")
SKILL_NAME_PATTERN = re.compile(r"^(?!.*--)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$")


def load_json(relative: str) -> dict:
    value = json.loads((ROOT / relative).read_text(encoding="utf-8"))
    if not isinstance(value, dict):
        raise AssertionError(f"{relative} must contain a JSON object")
    return value


def require(condition: bool, message: str) -> None:
    if not condition:
        raise AssertionError(message)


def require_contained_path(value: str, label: str) -> None:
    require(value.startswith("./"), f"{label} must start with ./")
    resolved = (ROOT / value).resolve()
    require(resolved.is_relative_to(ROOT), f"{label} escapes the plugin root")
    require(resolved.exists(), f"{label} points to missing path {value}")


def scalar(frontmatter: str, field: str) -> str:
    match = re.search(rf"^{re.escape(field)}:\s*(.+)$", frontmatter, re.MULTILINE)
    require(match is not None, f"skill frontmatter is missing {field}")
    value = match.group(1).strip()
    if value.startswith('"') and value.endswith('"'):
        return json.loads(value)
    if value.startswith("'") and value.endswith("'"):
        return value[1:-1].replace("''", "'")
    return value


portable = load_json("plugin.json")
require(set(portable) <= PORTABLE_FIELDS, "portable plugin.json has unknown top-level fields")
require(portable.get("$schema") == PLUGIN_SCHEMA, "portable plugin schema identifier is wrong")
require(portable.get("name") == "carbon", "portable plugin name is wrong")
require(NAME_PATTERN.fullmatch(portable["name"]) is not None, "portable plugin name is invalid")
require(portable.get("version") == VERSION, "portable plugin version is not aligned")
require(isinstance(portable.get("description"), str) and portable["description"], "portable description is missing")
require(set(portable.get("author", {})) <= {"name", "email", "url"}, "portable author has unknown fields")

portable_mcp = load_json("mcp.json")
require(set(portable_mcp) == {"$schema", "mcpServers"}, "portable mcp.json has unknown fields")
require(portable_mcp["$schema"] == MCP_SCHEMA, "portable MCP schema identifier is wrong")
servers = portable_mcp["mcpServers"]
require(isinstance(servers, dict) and set(servers) == {"carbon"}, "portable MCP server map is wrong")
server = servers["carbon"]
require(set(server) <= {"type", "command", "args", "env", "cwd"}, "portable MCP server has unknown fields")
require(server.get("type") == "stdio", "portable MCP transport must be stdio")
require(server.get("command") == "node", "portable MCP executable must be one token")
require(server.get("args") == ["${PLUGIN_ROOT}/server/index.js"], "portable MCP args must use PLUGIN_ROOT")
require(server.get("cwd") == "${PLUGIN_ROOT}", "portable MCP cwd must use PLUGIN_ROOT")

openai = load_json(".codex-plugin/plugin.json")
require(openai.get("name") == "carbon", "OpenAI plugin name is wrong")
require(str(openai.get("version", "")).split("+")[0] == VERSION, "OpenAI plugin version is not aligned")
for field in ("skills", "mcpServers"):
    require_contained_path(openai[field], f"OpenAI {field}")
interface = openai.get("interface", {})
for field in ("composerIcon", "logo"):
    require_contained_path(interface[field], f"OpenAI interface.{field}")
require(interface.get("displayName") == "CARBON — Engineering Confidence", "OpenAI display name is wrong")

openai_mcp = load_json(".mcp.json")
require(set(openai_mcp) == {"mcpServers"}, "OpenAI .mcp.json must use the host-validator wrapper")
require(set(openai_mcp["mcpServers"]) == {"carbon"}, "OpenAI MCP server map is wrong")
require(openai_mcp["mcpServers"]["carbon"].get("command") == "node", "OpenAI MCP executable is wrong")
require(openai_mcp["mcpServers"]["carbon"].get("args") == ["./server/index.js"], "OpenAI MCP entry path is wrong")

cursor = load_json(".cursor-plugin/plugin.json")
require(cursor.get("name") == "carbon", "Cursor plugin name is wrong")
require(cursor.get("version") == VERSION, "Cursor plugin version is not aligned")
require_contained_path(cursor["skills"], "Cursor skills")
require_contained_path(cursor["mcpServers"], "Cursor mcpServers")
require_contained_path("./" + cursor["logo"].lstrip("./"), "Cursor logo")

claude = load_json(".claude-plugin/plugin.json")
require(claude.get("version") == VERSION, "Claude plugin version is not aligned")
require("mcpServers" not in claude, "Claude discovers MCP servers from root .mcp.json, not its plugin manifest")
claude_mcp = load_json(".mcp.json")
require(set(claude_mcp) == {"mcpServers"}, "Claude MCP file must use the native wrapper")
require(set(claude_mcp["mcpServers"]) == {"carbon"}, "Claude MCP server map is wrong")
claude_server = claude_mcp["mcpServers"]["carbon"]
require(claude_server.get("command") == "node", "Claude MCP executable is wrong")
require(
    claude_server.get("cwd") == "." and claude_server.get("args") == ["./server/index.js"],
    "Claude MCP entry must resolve relative to the installed plugin root",
)

package = load_json("package.json")
server_package = load_json("server/package.json")
require(package.get("version") == VERSION, "root package version is not aligned")
require(server_package.get("version") == VERSION, "server package version is not aligned")

skill_files = sorted((ROOT / "skills").glob("*/SKILL.md"))
require(len(skill_files) == 44, f"expected 44 immediate skills, found {len(skill_files)}")
for skill_file in skill_files:
    text = skill_file.read_text(encoding="utf-8")
    require(text.startswith("---\n"), f"{skill_file} has no opening YAML delimiter")
    parts = text.split("\n---\n", 1)
    require(len(parts) == 2 and parts[1].strip(), f"{skill_file} has invalid frontmatter or no body")
    frontmatter = parts[0][4:]
    top_level = {
        match.group(1)
        for match in re.finditer(r"^([A-Za-z0-9-]+):", frontmatter, re.MULTILINE)
    }
    require(top_level <= SKILL_FIELDS, f"{skill_file} has non-portable frontmatter fields: {top_level - SKILL_FIELDS}")
    name = scalar(frontmatter, "name")
    description = scalar(frontmatter, "description")
    require(name == skill_file.parent.name, f"{skill_file} name does not match its directory")
    require(SKILL_NAME_PATTERN.fullmatch(name) is not None and len(name) <= 64, f"{skill_file} name is invalid")
    require(1 <= len(description) <= 1024, f"{skill_file} description length is invalid")

print(
    "PASS: OpenAI ChatGPT/Codex, Agent Plugins 1.0, Cursor, Claude, portable MCP, "
    "version alignment, path containment, and 44 Agent Skills contracts verified."
)
