"""One native manifest/feed writer for admitted previews and protected releases."""
from __future__ import annotations

import base64
import hashlib
from pathlib import Path


from hermes_cli.release_channels import ChannelError, build_prefix, canonical_json, validate_manifest
from scripts.bundles.release_artifacts import single, validate_windows_bundle, write_appinstaller


ALL_TARGETS = tuple((platform, arch) for platform in ("macos", "windows") for arch in ("arm64", "x64"))


def _leg_sha512(root: Path, files: dict, arch: str) -> dict[str, str]:
    """sha512 per artifact from the receipt-bound per-arch electron feed (``<arch>-*-mac.yml``)."""
    import hermes_yaml as yaml

    leg = single(name for name in files if name.startswith(f"{arch}-") and name.endswith("-mac.yml"))
    if not (root / leg).is_file():
        raise ChannelError(f"Missing receipt-bound macOS feed leg: {leg}")
    feed = yaml.safe_load((root / leg).read_text(encoding="utf-8-sig"))
    digests = {}
    for entry in feed.get("files", []):
        name = str(entry.get("url", ""))
        if name not in files or entry.get("size") != files[name]["size"]:
            raise ChannelError(f"macOS feed leg {leg} disagrees with its receipts: {name}")
        digests[name] = str(entry["sha512"])
    return digests


def assemble(request: dict, native: dict, root: Path, *, artifact_prefix: str,
             targets=ALL_TARGETS, local_bytes: bool = True) -> tuple[dict, list[Path]]:
    """``targets`` defaults to every native target. A stable release that did not
    opt into macOS Intel passes the three it built; arm64 macOS and both Windows
    arches are always required.

    ``local_bytes=False`` trusts the receipts instead of downloaded packages:
    every artifact is bound by its receipt sha256 (and was validated when the
    candidate was accepted), the macOS sha512s come from the receipt-bound
    per-arch feed legs, and no multi-GB package needs to be on disk."""
    files = native["files"]
    rows = native["packages"]
    targets = sorted(targets)
    if not {("macos", "arm64"), ("windows", "arm64"), ("windows", "x64")} <= set(targets) \
            or not set(targets) <= set(ALL_TARGETS):
        raise ChannelError("Native targets must include macOS arm64 and both Windows architectures")
    if sorted((row["platform"], row["arch"]) for row in rows) != targets:
        raise ChannelError("Native metadata must cover both platforms and architectures")
    prefix = build_prefix(request["buildId"])
    base = request["publicBase"]
    windows = [row for row in rows if row["platform"] == "windows"]
    if windows[0]["applicationId"] != request["identity"]["appNamePascal"]:
        raise ChannelError("Windows application identity mismatch")
    bundle_name = single(name for name in files if name.endswith(".msixbundle") and not name.startswith("Store-"))
    if local_bytes:
        validate_windows_bundle(root / bundle_name, windows)
    packages, mac_files = [], []
    for row in rows:
        mac = row["platform"] == "macos"
        filename = row["filename"] if mac else bundle_name
        receipt = files.get(filename)
        if receipt is None or (local_bytes and not (root / filename).is_file()):
            raise ChannelError("Missing receipt-bound native artifact")
        signing = "teamId" if mac else "publisher"
        packages.append({"platform": "darwin" if mac else "win32", "arch": row["arch"], "variant": "bundled",
                         "identity": row["identity"], "version": row["version"], signing: row.get(signing),
                         "artifact": {"key": artifact_prefix + filename, "sha256": receipt["sha256"], "size": receipt["size"]},
                         "feed": {"key": prefix + ("darwin/stable-mac.yml" if mac else "win32/stable.appinstaller"),
                                  "channel": "stable"}})
        if mac:
            leg_digests = None if local_bytes else _leg_sha512(root, files, row["arch"])
            for name in (filename, filename.removesuffix(".zip") + ".dmg"):
                if any(item not in files or (local_bytes and not (root / item).is_file())
                       for item in (name, name + ".blockmap")):
                    raise ChannelError("Missing receipt-bound macOS package or blockmap")
                if leg_digests is not None:
                    if name not in leg_digests:
                        raise ChannelError(f"macOS feed leg has no sha512 for {name}")
                    digest = leg_digests[name]
                else:
                    with (root / name).open("rb") as stream:
                        digest = base64.b64encode(hashlib.file_digest(stream, "sha512").digest()).decode("ascii")
                mac_files.append({"url": f"{base}/{artifact_prefix}{name}", "sha512": digest, "size": files[name]["size"]})
    manifest = {"schema": 1, "request": request, "packages": packages}
    if all(row.get("receiverProtocol") == 1 for row in rows):
        manifest["receiverProtocol"] = 1
    tag = request.get("releaseTag", "")
    from hermes_cli.update_channel import is_canary_tag
    record = {"name": request["channel"], "repository": request["repository"], "identity": request["identity"],
              "policy": ("canary-release" if is_canary_tag(tag) else "stable-release") if tag else "preview", "head": None}
    validate_manifest(manifest, record, base)
    descriptor = root / "win32" / "stable.appinstaller"
    write_appinstaller(descriptor, identity=windows[0]["identity"], publisher=windows[0]["publisher"],
                       version=windows[0]["version"], self_uri=f"{base}/{prefix}win32/stable.appinstaller",
                       artifact_uri=f"{base}/{artifact_prefix}{bundle_name}", update_policy="pinned")
    feed = root / "darwin" / "stable-mac.yml"
    feed.parent.mkdir(parents=True, exist_ok=True)
    first = single(row for row in mac_files if row["url"].endswith("-arm64.zip"))
    # A neutral feed name avoids interpreting custom channel names as prereleases.
    feed.write_bytes(canonical_json({"version": request["version"], "files": mac_files,
                                    "path": first["url"], "sha512": first["sha512"]}))
    return manifest, [feed, descriptor]
