#!/usr/bin/env python3
from __future__ import annotations

import argparse
import hashlib
import json
import os
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import cadquery as cq
import OCP

VERSION = "0.1.0-real-foundation"

OUTPUT_ROOT = Path(
    "/SHiREVault/SHiRELimbs/CoverCanvas/GeometryJobs"
)


def utc_now() -> str:
    return datetime.now(timezone.utc).isoformat()


def sha256_json(value: Any) -> str:
    raw = json.dumps(
        value,
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
    ).encode("utf-8")
    return hashlib.sha256(raw).hexdigest()


def sha256_file(path: Path) -> str:
    digest = hashlib.sha256()

    with path.open("rb") as handle:
        while True:
            chunk = handle.read(1024 * 1024)
            if not chunk:
                break
            digest.update(chunk)

    return digest.hexdigest()


def write_json_atomic(path: Path, value: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)

    temporary = path.with_suffix(
        path.suffix + ".tmp"
    )

    with temporary.open(
        "w",
        encoding="utf-8",
    ) as handle:
        json.dump(
            value,
            handle,
            indent=2,
            ensure_ascii=False,
        )
        handle.write("\n")
        handle.flush()
        os.fsync(handle.fileno())

    os.replace(temporary, path)


def preflight(payload: dict[str, Any]) -> list[str]:
    errors: list[str] = []

    cover = payload.get("cover_model")
    if not isinstance(cover, dict):
        return ["cover_model is required"]

    required = {
        "id": "Validated CoverModel id is missing",
        "reference": "Validated LimbForge base file is missing",
        "limbforge_project_uuid": "LimbForge project UUID is missing",
        "validated_revision": "Validated base revision is missing",
        "base_sha256": "Validated base SHA-256 is missing",
        "protected_zone_revision": "Protected-zone revision is missing",
    }

    for key, message in required.items():
        if not cover.get(key):
            errors.append(message)

    if cover.get("authority") != "limbforge":
        errors.append(
            "Cover authority must be LimbForge"
        )

    if cover.get("validation_status") != "validated":
        errors.append(
            "LimbForge cover is not validated"
        )

    if cover.get("protected_zones_resolved") is not True:
        errors.append(
            "Protected zones are unresolved"
        )

    reference = cover.get("reference")

    if reference:
        base_path = Path(str(reference))

        if not base_path.is_file():
            errors.append(
                "Validated base file does not exist"
            )
        elif cover.get("base_sha256"):
            actual = sha256_file(base_path)

            if actual.lower() != str(
                cover["base_sha256"]
            ).lower():
                errors.append(
                    "Validated base SHA-256 mismatch"
                )

    layers = payload.get("layers")

    if not isinstance(layers, list) or not layers:
        errors.append(
            "At least one visible artwork layer is required"
        )

    protected = payload.get("protected_zones")

    if not isinstance(protected, list):
        errors.append(
            "protected_zones must be supplied as an array"
        )

    return errors


def execute(
    payload: dict[str, Any],
    job_id: str | None = None,
) -> dict[str, Any]:
    job_id = job_id or ("ccgeo-" + str(uuid.uuid4()))

    allowed = (
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "0123456789-_"
    )

    if (
        not job_id.startswith("ccgeo-")
        or any(c not in allowed for c in job_id)
    ):
        raise ValueError("Invalid geometry job id")
    created = utc_now()
    job_root = OUTPUT_ROOT / job_id

    manifest: dict[str, Any] = {
        "id": job_id,
        "engine": "SHiRE CoverCanvas Geometry",
        "engine_version": VERSION,
        "simulation": False,
        "cadquery_version": cq.__version__,
        "ocp_version": getattr(
            OCP,
            "__version__",
            "unknown",
        ),
        "state": "preflight",
        "progress": 10,
        "created_time": created,
        "request_sha256": sha256_json(payload),
        "manufacturing_ready": False,
        "output_files": [],
    }

    job_root.mkdir(
        parents=True,
        exist_ok=False,
    )

    write_json_atomic(
        job_root / "request.json",
        payload,
    )

    errors = preflight(payload)

    if errors:
        manifest.update({
            "state": "blocked",
            "progress": 100,
            "completed_time": utc_now(),
            "failure_reason": (
                "LimbForge manufacturing prerequisites unresolved"
            ),
            "errors": errors,
        })

        write_json_atomic(
            job_root / "job-manifest.json",
            manifest,
        )

        return manifest

    manifest.update({
        "state": "blocked",
        "progress": 100,
        "completed_time": utc_now(),
        "failure_reason": (
            "Production CAD stages are not enabled yet"
        ),
        "errors": [
            "Preflight passed, but production geometry "
            "processing has not been promoted yet"
        ],
        "note": (
            "Validated LimbForge prerequisites passed. "
            "Manufacturing remains blocked until the "
            "real CAD processing stages are installed."
        ),
    })

    write_json_atomic(
        job_root / "job-manifest.json",
        manifest,
    )

    return manifest


def self_test() -> int:
    payload = {
        "cover_model": {
            "id": "SELFTEST-PENDING",
            "reference": None,
            "authority": None,
            "validation_status": "pending",
        },
        "layers": [{"id": "layer-selftest"}],
        "protected_zones": [],
    }

    result = execute(payload)

    print(json.dumps(
        result,
        indent=2,
        ensure_ascii=False,
    ))

    if result.get("state") != "blocked":
        raise RuntimeError(
            "Self-test failed: unsafe request was not blocked"
        )

    return 0


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--self-test", action="store_true")
    parser.add_argument("--request")
    parser.add_argument("--job-id")
    args = parser.parse_args()

    if args.self_test:
        return self_test()

    if not args.request:
        parser.error(
            "--request or --self-test is required"
        )

    with Path(args.request).open(
        "r",
        encoding="utf-8",
    ) as handle:
        payload = json.load(handle)

    if not isinstance(payload, dict):
        raise ValueError(
            "Request must be a JSON object"
        )

    print(json.dumps(
        execute(payload, job_id=args.job_id),
        indent=2,
        ensure_ascii=False,
    ))

    return 0


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