#!/usr/bin/env python3
"""Create one standalone, zero-grant Station connector authoring project."""

from __future__ import annotations

import argparse
import hashlib
import json
import os
import platform
from pathlib import Path
import re
import shutil
import sys


TYPE = re.compile(r"^[a-z][a-z0-9_-]*(?:\.[a-z0-9][a-z0-9_-]*)+$")
IDENTIFIER = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9_.:-]*[A-Za-z0-9])?$")
PACKAGE = re.compile(r"^[a-z0-9]+(?:[.-][a-z0-9]+)+(?:/[a-z0-9][a-z0-9._-]*)?$")
FIXED_TIME = "2026-01-01T00:00:00.000Z"


def fail(message: str) -> None:
    raise ValueError(message)


def object_value(value: object, label: str) -> dict:
    if not isinstance(value, dict):
        fail(f"{label} must be an object")
    return value


def string_value(value: object, label: str, pattern: re.Pattern[str] | None = None) -> str:
    if not isinstance(value, str) or not value.strip():
        fail(f"{label} must be a non-empty string")
    result = value.strip()
    if pattern is not None and not pattern.fullmatch(result):
        fail(f"{label} is not a stable public identifier")
    return result


def protocol_list(value: object, label: str, owner: str) -> list[dict]:
    if not isinstance(value, list) or not value:
        fail(f"{label} must contain at least one protocol")
    result = []
    types = set()
    for index, entry in enumerate(value):
        item = object_value(entry, f"{label}[{index}]")
        protocol_type = string_value(item.get("type"), f"{label}[{index}].type", TYPE)
        if protocol_type in types:
            fail(f"{label} contains duplicate protocol types")
        types.add(protocol_type)
        version = item.get("contractVersion")
        if isinstance(version, bool) or not isinstance(version, int) or version <= 0:
            fail(f"{label}[{index}].contractVersion must be a positive integer")
        payload_schema = object_value(item.get("payloadSchema"), f"{label}[{index}].payloadSchema")
        canonical_schema = json.dumps(payload_schema, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
        result.append({
            "type": protocol_type,
            "contractVersion": version,
            "payloadSchema": payload_schema,
            "payloadSchemaFile": f"schemas/{owner}-protocol-{index + 1:02d}.schema.json",
            "canonicalSchema": canonical_schema,
        })
    return result


def read_brief(path: Path) -> tuple[dict, dict, dict[str, str]]:
    try:
        brief = object_value(json.loads(path.read_text(encoding="utf-8")), "brief")
    except (OSError, json.JSONDecodeError) as error:
        fail(f"Could not read the connector authoring brief: {error}")
    if brief.get("schema") != "chasingmastery.station-connector-authoring-brief" or brief.get("schemaVersion") != 2:
        fail("The connector authoring brief must use schema version 2")

    identity = object_value(brief.get("identity"), "identity")
    source = object_value(brief.get("source"), "source")
    activity = object_value(brief.get("activityContract"), "activityContract")
    fixture = object_value(brief.get("firstFixture"), "firstFixture")
    authority = object_value(brief.get("authority"), "authority")
    requested = authority.get("requestedResources")
    if requested != []:
        fail("This authoring toolchain supports zero-grant connectors only; requestedResources must be empty")

    package_id = string_value(identity.get("packageId"), "identity.packageId", PACKAGE)
    contribution_id = string_value(identity.get("contributionId"), "identity.contributionId", TYPE)
    display_name = string_value(identity.get("displayName"), "identity.displayName")
    source_id = string_value(source.get("sourceId"), "source.sourceId", IDENTIFIER)
    source_type = string_value(source.get("sourceType"), "source.sourceType", TYPE)
    source_name = string_value(source.get("displayName"), "source.displayName")

    connector_protocols = protocol_list(brief.get("observations"), "observations", "connector")
    for index, entry in enumerate(brief.get("observations")):
        string_value(object_value(entry, f"observations[{index}]").get("purpose"), f"observations[{index}].purpose")
    protocol_types = [item["type"] for item in connector_protocols]

    activity_id = string_value(activity.get("contributionId"), "activityContract.contributionId", IDENTIFIER)
    accepted_protocols = protocol_list(activity.get("acceptedProtocols"), "activityContract.acceptedProtocols", "activity")
    if not any(
        connector["type"] == accepted["type"]
        and connector["contractVersion"] == accepted["contractVersion"]
        and connector["canonicalSchema"] == accepted["canonicalSchema"]
        for connector in connector_protocols
        for accepted in accepted_protocols
    ):
        fail("The connector and Practice Mode must share at least one exact observation protocol")
    manual_fallback = activity.get("manualFallback")
    if not isinstance(manual_fallback, bool):
        fail("activityContract.manualFallback must be boolean")

    fixture_id = string_value(fixture.get("id"), "firstFixture.id", IDENTIFIER)
    fixture_label = string_value(fixture.get("label"), "firstFixture.label")
    fixture_observations = fixture.get("observations")
    if not isinstance(fixture_observations, list) or not fixture_observations:
        fail("firstFixture.observations must contain at least one event")
    configured = []
    for index, entry in enumerate(fixture_observations):
        item = object_value(entry, f"firstFixture.observations[{index}]")
        event_id = string_value(item.get("sourceEventId"), f"firstFixture.observations[{index}].sourceEventId", IDENTIFIER)
        event_type = string_value(item.get("protocolType"), f"firstFixture.observations[{index}].protocolType", TYPE)
        if event_type not in protocol_types:
            fail(f"Fixture observation type {event_type} is not declared")
        payload = item.get("payload", {})
        if not isinstance(payload, dict):
            fail(f"firstFixture.observations[{index}].payload must be an object")
        configured.append({
            "sourceEventId": event_id,
            "protocolType": event_type,
            "occurredAt": FIXED_TIME,
            "observedAt": FIXED_TIME,
            "payload": payload,
        })

    configuration = {
        "sourceId": source_id,
        "sourceType": source_type,
        "observationProtocolType": protocol_types[0],
        "displayName": source_name,
        "inventoryObservedAt": FIXED_TIME,
        "observations": configured,
    }
    first = {
        "id": fixture_id,
        "label": fixture_label,
        "configuration": configuration,
        "expectedSourceIds": [source_id],
        "expectedProtocolTypes": [item["protocolType"] for item in configured],
        "expectedObservationCount": len(configured),
    }
    duplicate_configuration = dict(configuration)
    duplicate_configuration["observations"] = [configured[0], configured[0]]
    duplicate = {
        "id": "duplicate-source-event",
        "label": "Return a duplicate receipt for an exact source event replay",
        "configuration": duplicate_configuration,
        "expectedSourceIds": [source_id],
        "expectedProtocolTypes": [configured[0]["protocolType"], configured[0]["protocolType"]],
        "expectedObservationCount": 2,
    }
    descriptor = {
        "schema": "chasingmastery.station-connector-project",
        "schemaVersion": 2,
        "identity": {
            "packageId": package_id,
            "packageVersion": "0.1.0",
            "contributionId": contribution_id,
            "displayName": display_name,
            "binaryName": "cm-station-authored-connector",
        },
        "declaration": {
            "sourceTypes": [source_type],
            "observationProtocols": [{
                "type": item["type"],
                "contractVersion": item["contractVersion"],
                "payloadSchemaFile": item["payloadSchemaFile"],
            } for item in connector_protocols],
        },
        "requestedResources": [],
        "activityContract": {
            "contributionId": activity_id,
            "acceptedProtocols": [{
                "type": item["type"],
                "contractVersion": item["contractVersion"],
                "payloadSchemaFile": item["payloadSchemaFile"],
            } for item in accepted_protocols],
            "manualFallback": manual_fallback,
        },
        "scenarios": [first, duplicate],
    }
    schemas = {
        item["payloadSchemaFile"]: item["canonicalSchema"]
        for item in connector_protocols + accepted_protocols
    }
    return brief, descriptor, schemas


def verify_toolchain(path: Path) -> dict:
    manifest_path = path / "toolchain.json"
    try:
        manifest = object_value(json.loads(manifest_path.read_text(encoding="utf-8")), "toolchain")
    except (OSError, json.JSONDecodeError) as error:
        fail(f"Bundled connector toolchain is unavailable: {error}")
    if manifest.get("schema") != "chasingmastery.connector-author-toolchain" or manifest.get("schemaVersion") != 1:
        fail("Bundled connector toolchain manifest is invalid")
    files = manifest.get("files")
    if not isinstance(files, list) or not files:
        fail("Bundled connector toolchain has no reviewed files")
    for entry in files:
        item = object_value(entry, "toolchain.files[]")
        relative = string_value(item.get("file"), "toolchain.files[].file")
        expected = string_value(item.get("sha256"), "toolchain.files[].sha256")
        candidate = (path / relative).resolve()
        if path.resolve() not in candidate.parents or not candidate.is_file():
            fail(f"Bundled connector toolchain file is unavailable: {relative}")
        actual = hashlib.sha256(candidate.read_bytes()).hexdigest()
        if actual != expected:
            fail(f"Bundled connector toolchain digest mismatch: {relative}")
    return manifest


def host_target(manifest: dict) -> str:
    system = platform.system()
    machine = platform.machine().lower()
    if system == "Linux" and machine in ("x86_64", "amd64"):
        target = "linux-x86_64"
    elif system == "Darwin" and machine == "arm64":
        target = "darwin-arm64"
    else:
        fail("Connector authoring supports Linux x86-64 and Apple Silicon macOS; Windows and Intel macOS are not yet certified")
    targets = manifest.get("targets")
    if not isinstance(targets, list) or target not in targets:
        fail(f"Bundled connector toolchain does not contain the reviewed {target} profile")
    return target


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--directory", required=True)
    parser.add_argument("--brief", required=True)
    arguments = parser.parse_args()
    destination = Path(arguments.directory).expanduser().resolve()
    brief_path = Path(arguments.brief).expanduser().resolve()
    skill = Path(__file__).resolve().parent.parent
    template = skill / "assets" / "template"
    toolchain = skill / "assets" / "toolchain"

    try:
        brief, descriptor, schemas = read_brief(brief_path)
        manifest = verify_toolchain(toolchain)
        target = host_target(manifest)
        if not template.is_dir():
            fail("Bundled connector project template is unavailable")
        if destination.exists() and any(destination.iterdir()):
            fail("Destination must be absent or empty; refusing to overwrite existing work")
        destination.mkdir(parents=True, exist_ok=True)
        shutil.copytree(template, destination, dirs_exist_ok=True)
        shutil.copytree(toolchain, destination / ".chasing-mastery" / "toolchain", dirs_exist_ok=True)
        (destination / "chasing-mastery.connector-authoring.json").write_text(
            json.dumps(brief, indent=2) + "\n", encoding="utf-8"
        )
        (destination / "chasing-mastery.connector.json").write_text(
            json.dumps(descriptor, indent=2) + "\n", encoding="utf-8"
        )
        for relative, content in schemas.items():
            schema_path = destination / relative
            schema_path.parent.mkdir(parents=True, exist_ok=True)
            schema_path.write_text(content, encoding="utf-8")
        launcher = destination / ".chasing-mastery" / "run.sh"
        launcher.chmod(0o755)
        # Several standard ZIP extractors intentionally discard Unix mode bits.
        # Restore executable permission from the reviewed target manifest rather
        # than making archive behavior part of the authoring contract.
        target_toolchain = destination / ".chasing-mastery" / "toolchain" / target
        for binary_name in ("cm-station-connector-lab", "cm-station-sandbox"):
            (target_toolchain / binary_name).chmod(0o755)
        print(json.dumps({
            "schema": "chasingmastery.connector-bootstrap-result",
            "schemaVersion": 1,
            "status": "ready",
            "directory": str(destination),
            "zeroGrantProfile": True,
            "toolchainVersion": manifest.get("version"),
            "hostTarget": target,
            "nextActions": [
                ".chasing-mastery/run.sh status",
                "Implement the real connector through the copied public Rust SDK",
                ".chasing-mastery/run.sh test",
            ],
        }, indent=2))
        return 0
    except (ValueError, OSError) as error:
        print(json.dumps({
            "schema": "chasingmastery.connector-bootstrap-result",
            "schemaVersion": 1,
            "status": "failed",
            "message": str(error),
        }, indent=2), file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
