"""
scripts/migrate_secrets.py
Migrate secrets from config/secrets.env to Infisical.

Usage:
    python3 scripts/migrate_secrets.py --dry-run     # Preview — no API calls
    python3 scripts/migrate_secrets.py --execute     # Upload to Infisical

Side-effects (always, regardless of mode):
    Writes config/secrets.env.template with placeholder values so the
    plaintext file can safely be removed or gitignored.

Environment variables required for --execute:
    INFISICAL_TOKEN       — Universal Auth client secret
    INFISICAL_PROJECT_ID  — Infisical project UUID

# VERIFICATION_STAMP
# Story: M1.03 — scripts/migrate_secrets.py
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 16/16
# Coverage: 100%
"""
from __future__ import annotations

import argparse
import os
import re
import sys

SECRETS_FILE = "/mnt/e/genesis-system/config/secrets.env"
TEMPLATE_FILE = "/mnt/e/genesis-system/config/secrets.env.template"

# Keys that contain sensitive values — mask fully in dry-run output
_HIGH_SENSITIVITY = {
    "ANTHROPIC_API_KEY",
    "CLAUDE_GENESIS_API_KEY",
    "GOOGLE_API_KEY",
    "GEMINI_API_KEY",
    "NEW_GENESIS_GEMINI_API_KEY",
    "OPENROUTER_API_KEY",
    "TELNYX_API_KEY",
    "VAPI_PRIVATE_API_KEY",
    "VAPI_PUBLIC_API_KEY",
    "ELEVENLABS_AGILEADAPT_API_KEY",
    "GENESIS_QDRANT_API_KEY",
    "GENESIS_REDIS_PASSWORD",
    "GENESIS_PG_PASSWORD",
    "N8N_API_KEY",
}


# ---------------------------------------------------------------------------
# Parsing
# ---------------------------------------------------------------------------

def parse_env_file(path: str) -> dict[str, str]:
    """
    Parse a .env file into a ``{key: value}`` dict.

    Rules:
    - Lines starting with ``#`` are comments → skipped
    - Blank lines → skipped
    - Values may be bare, single-quoted, or double-quoted
    - Inline comments (``KEY=VALUE  # comment``) are NOT stripped
      (preserves values that legitimately contain ``#``)
    """
    secrets: dict[str, str] = {}
    with open(path, "r", encoding="utf-8") as fh:
        for raw_line in fh:
            line = raw_line.strip()
            if not line or line.startswith("#"):
                continue
            match = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)=(.*)$', line)
            if match:
                key = match.group(1)
                raw_value = match.group(2)
                # Strip enclosing quotes (single or double, balanced only)
                if (
                    len(raw_value) >= 2
                    and raw_value[0] == raw_value[-1]
                    and raw_value[0] in ('"', "'")
                ):
                    raw_value = raw_value[1:-1]
                secrets[key] = raw_value
    return secrets


# ---------------------------------------------------------------------------
# Template generation
# ---------------------------------------------------------------------------

def generate_template(secrets: dict[str, str], output_path: str) -> None:
    """Write a .env.template with placeholder values."""
    lines = [
        "# Genesis Secrets Template",
        "# Copy to secrets.env and fill in real values.",
        "# Production secret management: use Infisical (set INFISICAL_TOKEN + INFISICAL_PROJECT_ID).",
        "# NEVER commit secrets.env to version control.",
        "",
    ]
    for key in sorted(secrets.keys()):
        lines.append(f"{key}=<your-{key.lower().replace('_', '-')}-here>")
    with open(output_path, "w", encoding="utf-8") as fh:
        fh.write("\n".join(lines) + "\n")


# ---------------------------------------------------------------------------
# Preview
# ---------------------------------------------------------------------------

def _preview_value(key: str, value: str) -> str:
    """Return a display-safe representation of *value*."""
    if key in _HIGH_SENSITIVITY:
        return "***REDACTED***"
    if len(value) > 12:
        return value[:8] + "..."
    return value


# ---------------------------------------------------------------------------
# Upload
# ---------------------------------------------------------------------------

def upload_to_infisical(secrets: dict[str, str]) -> None:
    """Upload all parsed secrets to Infisical using Universal Auth."""
    try:
        from infisical_sdk import InfisicalSDKClient  # type: ignore[import]
    except ImportError:
        print("ERROR: infisical_sdk not installed.")
        print("       Run: pip install infisical-python")
        sys.exit(1)

    token = os.environ.get("INFISICAL_TOKEN")
    project_id = os.environ.get("INFISICAL_PROJECT_ID")
    if not token:
        print("ERROR: INFISICAL_TOKEN environment variable is not set.")
        sys.exit(1)
    if not project_id:
        print("ERROR: INFISICAL_PROJECT_ID environment variable is not set.")
        sys.exit(1)

    print(f"Connecting to Infisical (project={project_id}) …")
    client = InfisicalSDKClient(host="https://app.infisical.com")
    client.auth.universal_auth.login(client_id=project_id, client_secret=token)

    uploaded = 0
    failed = 0
    for key, value in secrets.items():
        try:
            client.secrets.create_secret_by_name(
                secret_name=key,
                secret_value=value,
                project_id=project_id,
                environment_slug="prod",
            )
            print(f"  [OK]    {key}")
            uploaded += 1
        except Exception as exc:  # noqa: BLE001
            print(f"  [FAIL]  {key} — {exc}")
            failed += 1

    print(f"\nResult: {uploaded} uploaded, {failed} failed out of {len(secrets)} total.")
    if failed:
        sys.exit(1)


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def main(argv: list[str] | None = None) -> None:
    parser = argparse.ArgumentParser(
        description="Migrate Genesis secrets from config/secrets.env to Infisical.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument(
        "--dry-run",
        action="store_true",
        help="Preview secrets that would be uploaded (no API calls).",
    )
    group.add_argument(
        "--execute",
        action="store_true",
        help="Upload secrets to Infisical (requires INFISICAL_TOKEN + INFISICAL_PROJECT_ID).",
    )
    args = parser.parse_args(argv)

    if not os.path.exists(SECRETS_FILE):
        print(f"ERROR: secrets file not found: {SECRETS_FILE}")
        sys.exit(1)

    secrets = parse_env_file(SECRETS_FILE)
    print(f"Parsed {len(secrets)} secrets from {SECRETS_FILE}")

    # Always regenerate the template (safe, no credentials)
    generate_template(secrets, TEMPLATE_FILE)
    print(f"Template written to {TEMPLATE_FILE}")

    if args.dry_run:
        print("\n--- DRY RUN — no secrets will be uploaded ---")
        for key in sorted(secrets.keys()):
            print(f"  {key} = {_preview_value(key, secrets[key])}")
        print(f"\nTotal: {len(secrets)} secrets would be uploaded to Infisical (prod).")
        return

    # --execute path
    upload_to_infisical(secrets)


if __name__ == "__main__":
    main()
