"""
BizNode Package Builder
========================
Builds distribution zip files for Basic and Pro tiers.

Usage:
    python scripts/build_package.py --tier basic     # Basic package (core only)
    python scripts/build_package.py --tier pro        # Pro package (everything)
    python scripts/build_package.py --tier basic --output dist/custom_name.zip

The Basic package excludes all Pro-only modules.
The Pro package includes everything except secrets and runtime data.
"""

import argparse
import hashlib
import json
import os
import sys
import zipfile
from datetime import datetime
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent
DIST_DIR = PROJECT_ROOT / "dist"

# ---------------------------------------------------------------------------
# Exclusion rules
# ---------------------------------------------------------------------------

# Always excluded (both tiers) — secrets, runtime data, build artifacts
ALWAYS_EXCLUDE = [
    ".env",
    ".env.*",
    ".env.txt",
    ".registration",
    ".license_key",
    ".git/",
    ".git\\",
    "__pycache__/",
    "__pycache__\\",
    "*.pyc",
    "*.pyo",
    # Runtime data
    "memory/qdrant/",
    "memory/ollama/",
    "memory/biznode.db",
    "memory/biznode.db-journal",
    "memory/biznode.db-wal",
    "backups/",
    "logs/",
    "bot.log",
    "dashboard.log",
    # Identity / keys
    "identity/*.pem",
    "backup/token.json",
    "backup/credentials.json",
    "backup/credentials/",
    "backup/last_backup.json",
    # Build artifacts
    "dist/",
    "_pyarmor_build/",
    "pyarmor_runtime_000000/",
    # Server-side code
    "serversidescript/",
    # Qdrant binary (user downloads their own)
    "memory/qdrant/qdrant.exe",
    "memory/qdrant/collections/",
    "memory/qdrant/aliases/",
    "memory/qdrant/raft_state.json",
    "memory/qdrant/tmp/",
    # Other
    ".qdrant-initialized",
    "output/",
    "channel.pdf",
    "node_modules/",
    # Admin-only features (not distributed in any tier)
    "videoautomation/",
    "marketingteam/",
    "generate_voiceover.py",
    "render_video.py",
    "voiceover/",
    "voiceover_script.txt",
    # Node monitoring (admin-only — never in client packages)
    "services/node_automonitor.py",
    "services/node_orchestrator.py",
    "services/reputation_service.py",
    "services/hosted_bot_runner.py",
    "scripts/telegram_bridge.py",
    "scripts/deploy_node.py",
    "scripts/setup_node.py",
    "scripts/node_bootstrap.py",
    "scripts/install_basic_node.py",
    "scripts/install_pro_node.py",
]

# Additional exclusions for Basic tier (Pro-only modules)
BASIC_EXCLUDE = [
    # Pro plugins
    "bizchannel/",
    "tools/admin/",
    # Pro-only services
    "services/affiliate_service.py",
    "services/bizbrain_service.py",
    "services/crypto_service.py",
    "services/customer_engagement_service.py",
    "services/group_ad_service.py",
    "services/stripe_service.py",
    "services/promo_email_service.py",
    "services/transaction_service.py",
    "services/workflow_template_service.py",
    # Pro-only scripts
    "seed_architecture_kb.py",
    "seed_kb_admin.py",
    # Pro docs
    "NODE_DIVERGENCE.md",
    "TECHNICAL_DOCUMENTATION.md",
]

# Pro tier: exclude admin-only BizChannel infrastructure (keep only client/)
PRO_EXCLUDE = [
    "bizchannel/ai/",
    "bizchannel/api/",
    "bizchannel/bots/",
    "bizchannel/database/",
    "bizchannel/discovery/",
    "bizchannel/events/",
    "bizchannel/marketing/",
    "bizchannel/website/",
    "bizchannel/workers/",
    "bizchannel/seed_data.py",
    "bizchannel/docs/",
    "tools/admin/",
]


def _should_exclude(rel_path: str, tier: str) -> bool:
    """Check if a file should be excluded from the package."""
    rel_path_unix = rel_path.replace("\\", "/")

    for pattern in ALWAYS_EXCLUDE:
        pattern = pattern.replace("\\", "/")
        if pattern.endswith("/"):
            # Directory pattern
            if rel_path_unix.startswith(pattern) or f"/{pattern}" in f"/{rel_path_unix}":
                return True
        elif "*" in pattern:
            # Wildcard pattern
            import fnmatch
            basename = os.path.basename(rel_path)
            if fnmatch.fnmatch(basename, pattern):
                return True
            if fnmatch.fnmatch(rel_path_unix, pattern):
                return True
        else:
            # Exact match
            if rel_path_unix == pattern or rel_path_unix.endswith(f"/{pattern}"):
                return True

    tier_exclude = []
    if tier == "basic":
        tier_exclude = BASIC_EXCLUDE
    elif tier == "pro":
        tier_exclude = PRO_EXCLUDE

    for pattern in tier_exclude:
        pattern = pattern.replace("\\", "/")
        if pattern.endswith("/"):
            if rel_path_unix.startswith(pattern) or f"/{pattern}" in f"/{rel_path_unix}":
                return True
        else:
            if rel_path_unix == pattern or rel_path_unix.endswith(f"/{pattern}"):
                return True

    return False


def build_package(tier: str, output_path: str = None) -> str:
    """
    Build a distribution zip for the given tier.
    Returns the path to the created zip file.
    """
    if tier not in ("basic", "pro"):
        raise ValueError(f"Invalid tier: {tier}. Must be 'basic' or 'pro'.")

    DIST_DIR.mkdir(parents=True, exist_ok=True)

    if output_path is None:
        date_str = datetime.now().strftime("%Y%m%d")
        output_path = str(DIST_DIR / f"biznode-{tier}-{date_str}.zip")

    included = []
    excluded = []
    file_checksums = {}  # path -> {md5, size}

    # Read version
    version_file = PROJECT_ROOT / "VERSION"
    version = version_file.read_text().strip() if version_file.exists() else "0.0.0"

    with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for root, dirs, files in os.walk(str(PROJECT_ROOT)):
            # Skip hidden directories and __pycache__
            dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__"]

            for filename in files:
                filepath = os.path.join(root, filename)
                rel_path = os.path.relpath(filepath, str(PROJECT_ROOT))

                if _should_exclude(rel_path, tier):
                    excluded.append(rel_path)
                    continue

                # Add to zip with "biznode/" prefix
                arcname = os.path.join("biznode", rel_path)
                zf.write(filepath, arcname)
                included.append(rel_path)

                # Compute checksum for manifest
                with open(filepath, "rb") as fh:
                    content = fh.read()
                rel_unix = rel_path.replace("\\", "/")
                file_checksums[rel_unix] = {
                    "md5": hashlib.md5(content).hexdigest(),
                    "size": len(content),
                }

        # Embed admin's public key for auto-trust
        admin_pub_key = PROJECT_ROOT / "identity" / "node_public.pem"
        if admin_pub_key.exists():
            arcname = os.path.join("biznode", "identity", "admin_public.pem")
            zf.write(str(admin_pub_key), arcname)
            included.append("identity/admin_public.pem")
            print(f"  [admin key] Embedded admin public key for auto-trust")

    size_mb = os.path.getsize(output_path) / (1024 * 1024)

    # Write manifest.json alongside the zip
    manifest_path = os.path.join(os.path.dirname(output_path), "manifest.json")
    manifest = {
        "version": version,
        "tier": tier,
        "built": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "total_files": len(included),
        "total_size_mb": round(size_mb, 2),
        "files": file_checksums,
    }
    with open(manifest_path, "w", encoding="utf-8") as mf:
        json.dump(manifest, mf, indent=2)

    # Copy VERSION to download dir
    version_dst = os.path.join(os.path.dirname(output_path), "VERSION")
    with open(version_dst, "w") as vf:
        vf.write(version)

    print()
    print("=" * 60)
    print(f"  BizNode {tier.upper()} Package Built")
    print("=" * 60)
    print(f"  Version   : {version}")
    print(f"  Output    : {output_path}")
    print(f"  Manifest  : {manifest_path}")
    print(f"  Size      : {size_mb:.2f} MB")
    print(f"  Files     : {len(included)} included, {len(excluded)} excluded")
    print(f"  Tier      : {tier.upper()}")
    print()

    if tier == "basic":
        print("  Excluded Pro modules:")
        for pattern in BASIC_EXCLUDE:
            print(f"    - {pattern}")
        print()

    print("=" * 60)

    return output_path


def main():
    parser = argparse.ArgumentParser(description="BizNode Package Builder")
    parser.add_argument("--tier", choices=["basic", "pro"], required=True,
                        help="License tier to build for")
    parser.add_argument("--output", type=str, default=None,
                        help="Output zip file path (default: dist/biznode-{tier}-{date}.zip)")
    parser.add_argument("--list", action="store_true",
                        help="List files that would be included without building")

    args = parser.parse_args()

    if args.list:
        print(f"\nFiles for {args.tier.upper()} package:\n")
        count = 0
        for root, dirs, files in os.walk(str(PROJECT_ROOT)):
            dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__"]
            for filename in files:
                filepath = os.path.join(root, filename)
                rel_path = os.path.relpath(filepath, str(PROJECT_ROOT))
                if not _should_exclude(rel_path, args.tier):
                    print(f"  {rel_path}")
                    count += 1
        print(f"\n  Total: {count} files\n")
    else:
        build_package(args.tier, args.output)


if __name__ == "__main__":
    main()
