"""SpecPack compiler — transforms PRD JSON into a content-addressed SpecPack."""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import jsonschema

from abi_spec.canonical import canonical_json, sha256_bytes
from abi_spec.traceability import generate_requirements_index, validate_req_id

_SCHEMA_DIR = Path(__file__).resolve().parent / "schemas"
_MANIFEST_SCHEMA_PATH = _SCHEMA_DIR / "spec_manifest.schema.json"


def _load_manifest_schema() -> dict[str, Any]:
    return json.loads(_MANIFEST_SCHEMA_PATH.read_text(encoding="utf-8"))


def _derive_spec_id(prd: dict[str, Any]) -> str:
    """Derive a spec_id from the PRD content.

    Uses the first requirement ID lowercased and prefixed with 'spec-'.
    """
    reqs = prd.get("requirements", [])
    if reqs:
        first_id = reqs[0].get("id", "unknown")
        return f"spec-{first_id.lower()}"
    return "spec-unknown"


def compile_prd_json_to_specpack(prd_json_path: str | Path, out_dir: str | Path) -> dict[str, str]:
    """Compile a PRD JSON file into a SpecPack directory.

    Args:
        prd_json_path: Path to the PRD JSON input file.
        out_dir: Directory where SpecPack files will be written.

    Returns:
        Dict with keys 'manifest_hash' and 'prd_hash'.

    Raises:
        ValueError: If the PRD JSON is invalid.
        jsonschema.ValidationError: If the manifest fails schema validation.
    """
    prd_json_path = Path(prd_json_path)
    out_dir = Path(out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)

    # Read and parse PRD
    prd_raw = prd_json_path.read_text(encoding="utf-8")
    prd = json.loads(prd_raw)

    if not isinstance(prd, dict) or "requirements" not in prd:
        raise ValueError("PRD JSON must be an object with a 'requirements' array")

    # Validate REQ-IDs
    requirements = prd["requirements"]
    for req in requirements:
        req_id = req.get("id", "")
        validate_req_id(req_id)

    # Compute PRD hash from canonical form
    prd_hash = sha256_bytes(canonical_json(prd).encode("utf-8"))

    # Generate requirements index
    requirements_index = generate_requirements_index(requirements)

    # Build spec manifest
    manifest: dict[str, Any] = {
        "spec_version": "1.0",
        "spec_id": _derive_spec_id(prd),
        "prd_hash": prd_hash,
        "requirements": requirements,
        "requirements_index": requirements_index,
    }

    # Validate against schema
    schema = _load_manifest_schema()
    jsonschema.validate(manifest, schema)

    # Write spec_manifest.json (canonical form for determinism)
    manifest_bytes = canonical_json(manifest).encode("utf-8")
    manifest_path = out_dir / "spec_manifest.json"
    manifest_path.write_bytes(manifest_bytes)

    manifest_hash = sha256_bytes(manifest_bytes)

    # Write hashes.json
    hashes = {
        "manifest_hash": manifest_hash,
        "prd_hash": prd_hash,
    }
    hashes_bytes = canonical_json(hashes).encode("utf-8")
    hashes_path = out_dir / "hashes.json"
    hashes_path.write_bytes(hashes_bytes)

    return {"manifest_hash": manifest_hash, "prd_hash": prd_hash}


def validate_specpack(specpack_dir: str | Path) -> bool:
    """Validate an existing SpecPack directory.

    Checks:
    - spec_manifest.json exists and is schema-valid
    - hashes.json exists and hashes match recomputed values

    Returns:
        True if valid.

    Raises:
        ValueError: If validation fails.
    """
    specpack_dir = Path(specpack_dir)

    manifest_path = specpack_dir / "spec_manifest.json"
    hashes_path = specpack_dir / "hashes.json"

    if not manifest_path.exists():
        raise ValueError(f"Missing spec_manifest.json in {specpack_dir}")
    if not hashes_path.exists():
        raise ValueError(f"Missing hashes.json in {specpack_dir}")

    # Read and validate manifest
    manifest_bytes = manifest_path.read_bytes()
    manifest = json.loads(manifest_bytes)
    schema = _load_manifest_schema()
    jsonschema.validate(manifest, schema)

    # Read hashes and verify
    hashes = json.loads(hashes_path.read_text(encoding="utf-8"))
    expected_manifest_hash = hashes.get("manifest_hash")
    actual_manifest_hash = sha256_bytes(manifest_bytes)

    if expected_manifest_hash != actual_manifest_hash:
        raise ValueError(
            f"Manifest hash mismatch: expected {expected_manifest_hash}, "
            f"got {actual_manifest_hash}"
        )

    return True
