#!/usr/bin/env python3
"""
Shared helpers for ios-native-testflight read_config.py.

Now that ci.config.yaml is gone, this module only contains the helpers
that interact with the filesystem or App Store Connect — no YAML or
config-precedence logic remains.

Contents:
    emit                $GITHUB_ENV writer (handles multi-line via heredoc)
    find_p8             Locate ASC .p8 key in creds/
    derive_team_if_empty  ASC API fallback for team_id
    lookup_app_id_via_api  Subprocess helper — calls lookup_app_id.py
"""

from __future__ import annotations

import os
import re
import subprocess
import sys
from pathlib import Path

from asc_common import make_jwt
from cfg_io import fail, log
from team_resolver import derive_team_id


AUTH_KEY_RE = re.compile(
    r"^AuthKey_([A-Z0-9]{8,10})(?:_Issuer_([0-9a-fA-F-]{36}))?\.p8$"
)
METADATA_KEY_RE = re.compile(
    r"(?:^|[(_-])key_id_([A-Z0-9]{8,10})_issuer_([0-9a-fA-F-]{36})"
    r"(?:_vendor_id_[0-9]+)?\)?\.p8$",
    re.IGNORECASE,
)


def parse_p8_filename(name: str) -> tuple[str, str | None] | None:
    """Parse canonical AuthKey names and the existing metadata-rich format."""
    canonical = AUTH_KEY_RE.match(name)
    if canonical:
        return canonical.group(1), canonical.group(2)
    metadata = METADATA_KEY_RE.search(name)
    if metadata:
        return metadata.group(1).upper(), metadata.group(2)
    return None


def emit(env_file: Path, name: str, value: str) -> None:
    """Append NAME=VALUE (or multi-line NAME<<EOF ... EOF) to $GITHUB_ENV."""
    if value is None:
        value = ""
    if "\n" in value:
        delim = "EOF"
        while delim in value:
            delim += "X"
        with env_file.open("a") as fh:
            fh.write(f"{name}<<{delim}\n{value}\n{delim}\n")
    else:
        with env_file.open("a") as fh:
            fh.write(f"{name}={value}\n")


def find_p8(workspace: Path) -> tuple[Path, str, str | None]:
    """Locate the ASC .p8 key. Returns (path, key_id, issuer_from_filename).

    If multiple keys are found we fail with an actionable message — there is
    no longer a config-based disambiguation channel.
    """
    creds_dir = workspace / "creds"
    matches = sorted(creds_dir.glob("*.p8"))
    if not matches:
        fail(
            "No ASC key found. Place "
            "AuthKey_<KEY_ID>_Issuer_<ISSUER_UUID>.p8 in creds/."
        )

    parsed: list[tuple[Path, str, str | None]] = []
    for p in matches:
        metadata = parse_p8_filename(p.name)
        if not metadata:
            log(f"skipping {p.name}: filename does not encode key_id and issuer")
            continue
        parsed.append((p, metadata[0], metadata[1]))

    if not parsed:
        fail(
            "Found .p8 file(s) in creds/ but none match the required naming "
            "pattern AuthKey_<KEY_ID>[_Issuer_<UUID>].p8 or "
            "*(key_id_<KEY_ID>_issuer_<UUID>_vendor_id_<ID>).p8"
        )
    if len(parsed) == 1:
        return parsed[0]

    names = ", ".join(p.name for p, _, _ in parsed)
    fail(
        f"Multiple ASC keys found in creds/ ({names}). Remove the unused "
        "ones — only a single key is supported."
    )


def derive_team_if_empty(
    team_val: str,
    team_src: str,
    creds: dict,
) -> tuple[str, str]:
    """If team_val is empty, derive from ASC API; return (value, source).

    Leaves non-empty team_val untouched. When derivation succeeds, source
    becomes ``derived_from_asc_key`` so read_config.py can log it clearly.
    When derivation returns empty, we preserve ("", "empty") so
    prepare_signing.py's post-profile-install fallback kicks in.
    """
    if team_val:
        return team_val, team_src
    try:
        token = make_jwt(creds["key_id"], creds["issuer_id"], creds["key_path"])
    except (OSError, ValueError) as exc:
        log(f"team derivation skipped: cannot sign ASC JWT ({exc!r})")
        return "", "empty"
    derived = derive_team_id(token)
    if not derived:
        return "", "empty"
    return derived, "derived_from_asc_key"


def lookup_app_id_via_api(bundle_id: str, scripts_dir: Path) -> str:
    """Invoke lookup_app_id.py to resolve apple_id via ASC API."""
    script = scripts_dir / "lookup_app_id.py"
    if not script.is_file():
        fail(f"lookup_app_id.py not found at {script}")
    env = dict(os.environ)
    env["BUNDLE_ID"] = bundle_id
    result = subprocess.run(
        [sys.executable, str(script)],
        env=env,
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        sys.stderr.write(result.stderr)
        fail(
            f"No ASC app found for bundle {bundle_id}. "
            "Run `fastlane create_app_ios` once manually to register the app."
        )
    return result.stdout.strip()
