#!/usr/bin/env python3
"""
Soleprint Build Tool

Builds the soleprint instance using modelgen for model generation.

Both dev and deploy modes copy files (no symlinks) for Docker compatibility.
After editing source files, re-run `python build.py dev` to update gen/.

Usage:
    python build.py dev                        # Build gen/ from source
    python build.py dev --cfg amar             # Include amar room config
    python build.py deploy --output /path/     # Build for production
    python build.py models                     # Only regenerate models

Examples:
    # Set up dev environment
    python build.py dev
    cd gen && .venv/bin/python run.py

    # With room config
    python build.py dev --cfg amar

    # Build for deployment
    python build.py deploy --output ../deploy/soleprint/
"""

import argparse
import logging
import os
import shutil
import subprocess
import sys
from pathlib import Path

# SPR root is where this script lives
SPR_ROOT = Path(__file__).resolve().parent

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format="%(message)s",
)
log = logging.getLogger(__name__)


def ensure_dir(path: Path):
    """Create directory if it doesn't exist."""
    path.mkdir(parents=True, exist_ok=True)


def copy_path(source: Path, target: Path):
    """Copy file or directory, resolving symlinks."""
    if target.is_symlink():
        target.unlink()
    elif target.exists():
        if target.is_dir():
            shutil.rmtree(target)
        else:
            target.unlink()

    if source.is_dir():
        shutil.copytree(source, target, symlinks=False)
        log.info(f"  Copied: {target.name}/ ({count_files(target)} files)")
    else:
        shutil.copy2(source, target)
        log.info(f"  Copied: {target.name}")


def count_files(path: Path) -> int:
    """Count files in directory recursively."""
    return sum(1 for _ in path.rglob("*") if _.is_file())


def generate_models(output_dir: Path):
    """Generate models using modelgen tool.

    Args:
        output_dir: Directory where models/pydantic/__init__.py will be created
    """
    config_path = SPR_ROOT / "cfg" / "soleprint.config.json"

    if not config_path.exists():
        log.warning(f"Config not found at {config_path}")
        return False

    # Soleprint-specific: models go in models/pydantic/__init__.py
    models_file = output_dir / "models" / "pydantic" / "__init__.py"
    models_file.parent.mkdir(parents=True, exist_ok=True)

    # Run modelgen as subprocess
    cmd = [
        sys.executable,
        "-m",
        "station.tools.modelgen",
        "from-config",
        "--config",
        str(config_path),
        "--output",
        str(models_file),
        "--format",
        "pydantic",
    ]

    result = subprocess.run(cmd, cwd=SPR_ROOT)
    return result.returncode == 0


def copy_cfg(output_dir: Path, cfg_name: str | None):
    """Copy configuration files to output directory.

    Args:
        output_dir: Target directory
        cfg_name: Name of room config (e.g., 'amar'), or None for base only
    """
    cfg_dir = output_dir / "cfg"
    ensure_dir(cfg_dir)

    # Always copy base config
    base_config = SPR_ROOT / "cfg" / "soleprint.config.json"
    if base_config.exists():
        copy_path(base_config, cfg_dir / "soleprint.config.json")

    # Copy room-specific config if specified
    if cfg_name:
        room_cfg = SPR_ROOT / "cfg" / cfg_name
        if room_cfg.exists() and room_cfg.is_dir():
            log.info(f"\nCopying {cfg_name} room config...")
            for item in room_cfg.iterdir():
                if item.name == ".env.example":
                    # Copy .env.example to output root as template
                    copy_path(item, output_dir / ".env.example")
                elif item.is_dir():
                    copy_path(item, cfg_dir / cfg_name / item.name)
                else:
                    ensure_dir(cfg_dir / cfg_name)
                    copy_path(item, cfg_dir / cfg_name / item.name)

            # Copy room-specific databrowse depot if exists
            room_databrowse = room_cfg / "databrowse" / "depot"
            if room_databrowse.exists():
                log.info(f"  Copying {cfg_name} databrowse depot...")
                target = output_dir / "station" / "monitors" / "databrowse" / "depot"
                copy_path(room_databrowse, target)

            # Copy room-specific tester tests if exists
            room_tests = room_cfg / "tester" / "tests"
            if room_tests.exists():
                log.info(f"  Copying {cfg_name} tester tests...")
                target = output_dir / "station" / "tools" / "tester" / "tests"
                copy_path(room_tests, target)

            # Copy room-specific monitors if exists
            room_monitors = room_cfg / "monitors"
            if room_monitors.exists():
                log.info(f"  Copying {cfg_name} monitors...")
                for monitor in room_monitors.iterdir():
                    if monitor.is_dir():
                        target = output_dir / "station" / "monitors" / monitor.name
                        copy_path(monitor, target)

            # Copy room-specific models if exists
            room_models = room_cfg / "models"
            if room_models.exists():
                log.info(f"  Copying {cfg_name} models...")
                target = output_dir / "models" / cfg_name
                copy_path(room_models, target)
        else:
            log.warning(f"Room config '{cfg_name}' not found at {room_cfg}")


def build_dev(output_dir: Path, cfg_name: str | None = None):
    """
    Build for development using copies (Docker-compatible).

    Structure:
        gen/
        ├── main.py
        ├── run.py
        ├── index.html
        ├── requirements.txt
        ├── Dockerfile
        ├── dataloader/
        ├── artery/
        ├── atlas/
        ├── station/
        ├── data/
        ├── cfg/
        ├── .env.example      # From cfg/<room>/.env.example
        └── models/           # Generated

    After editing source files, re-run `python build.py dev` to update gen/.
    """
    log.info("\n=== Building DEV environment ===")
    log.info(f"SPR root: {SPR_ROOT}")
    log.info(f"Output:   {output_dir}")
    if cfg_name:
        log.info(f"Room cfg: {cfg_name}")

    ensure_dir(output_dir)

    # Soleprint core files
    log.info("\nCopying soleprint files...")
    soleprint = SPR_ROOT / "soleprint"
    copy_path(soleprint / "main.py", output_dir / "main.py")
    copy_path(soleprint / "run.py", output_dir / "run.py")
    copy_path(soleprint / "index.html", output_dir / "index.html")
    copy_path(soleprint / "requirements.txt", output_dir / "requirements.txt")
    copy_path(soleprint / "dataloader", output_dir / "dataloader")
    if (soleprint / "Dockerfile").exists():
        copy_path(soleprint / "Dockerfile", output_dir / "Dockerfile")

    # System directories
    log.info("\nCopying systems...")
    for system in ["artery", "atlas", "station"]:
        source = SPR_ROOT / system
        if source.exists():
            copy_path(source, output_dir / system)

    # Data directory
    log.info("\nCopying data...")
    copy_path(SPR_ROOT / "data", output_dir / "data")

    # Config
    log.info("\nCopying config...")
    copy_cfg(output_dir, cfg_name)

    # Models (generated)
    log.info("\nGenerating models...")
    if not generate_models(output_dir):
        log.warning("Model generation failed, you may need to run it manually")

    log.info("\n✓ Dev build complete!")
    log.info(f"\nTo run:")
    log.info(f"  cd {output_dir}")
    log.info(f"  python3 -m venv .venv")
    log.info(f"  .venv/bin/pip install -r requirements.txt")
    log.info(f"  .venv/bin/python run.py       # Single-port bare-metal dev")
    log.info(f"\nAfter editing source, rebuild with: python build.py dev")


def build_deploy(output_dir: Path, cfg_name: str | None = None):
    """
    Build for deployment by copying all files (no symlinks).
    """
    log.info("\n=== Building DEPLOY package ===")
    log.info(f"SPR root: {SPR_ROOT}")
    log.info(f"Output:   {output_dir}")
    if cfg_name:
        log.info(f"Room cfg: {cfg_name}")

    if output_dir.exists():
        response = input(f"\nOutput directory exists. Overwrite? [y/N] ")
        if response.lower() != "y":
            log.info("Aborted.")
            return
        shutil.rmtree(output_dir)

    ensure_dir(output_dir)

    # Soleprint core files (copy)
    log.info("\nCopying soleprint files...")
    soleprint = SPR_ROOT / "soleprint"
    copy_path(soleprint / "main.py", output_dir / "main.py")
    copy_path(soleprint / "run.py", output_dir / "run.py")
    copy_path(soleprint / "index.html", output_dir / "index.html")
    copy_path(soleprint / "requirements.txt", output_dir / "requirements.txt")
    copy_path(soleprint / "dataloader", output_dir / "dataloader")
    if (soleprint / "Dockerfile").exists():
        copy_path(soleprint / "Dockerfile", output_dir / "Dockerfile")

    # System directories (copy)
    log.info("\nCopying systems...")
    for system in ["artery", "atlas", "station"]:
        source = SPR_ROOT / system
        if source.exists():
            copy_path(source, output_dir / system)

    # Data directory (copy)
    log.info("\nCopying data...")
    copy_path(SPR_ROOT / "data", output_dir / "data")

    # Config (copy)
    log.info("\nCopying config...")
    copy_cfg(output_dir, cfg_name)

    # Models (generate fresh) - pass output_dir, modelgen adds models/pydantic
    log.info("\nGenerating models...")
    if not generate_models(output_dir):
        # Fallback: copy from gen if exists
        existing = SPR_ROOT / "gen" / "models"
        if existing.exists():
            log.info("  Using existing models from gen/")
            copy_path(existing, output_dir / "models")

    # Copy schema.json for reference
    log.info("\nCopying schema...")
    copy_path(SPR_ROOT / "schema.json", output_dir / "schema.json")

    # Create run script
    run_script = output_dir / "start.sh"
    run_script.write_text("""#!/bin/bash
# Soleprint runner
cd "$(dirname "$0")"

if [ ! -d ".venv" ]; then
    echo "Creating virtual environment..."
    python3 -m venv .venv
    .venv/bin/pip install -r requirements.txt
fi

echo "Starting soleprint on http://localhost:12000"
.venv/bin/python main.py
""")
    run_script.chmod(0o755)
    log.info("  Created: start.sh")

    total_files = count_files(output_dir)
    log.info(f"\n✓ Deploy build complete! ({total_files} files)")
    log.info(f"\nTo run:")
    log.info(f"  cd {output_dir}")
    log.info(f"  ./start.sh")
    log.info(f"\nOr deploy to server:")
    log.info(f"  rsync -av {output_dir}/ server:/app/soleprint/")
    log.info(f"  ssh server 'cd /app/soleprint && ./start.sh'")


def build_models():
    """Only regenerate models."""
    log.info("\n=== Generating models only ===")

    output_dir = SPR_ROOT / "gen"
    ensure_dir(output_dir)

    if generate_models(output_dir):
        log.info("\n✓ Models generated!")
    else:
        log.error("Model generation failed")
        sys.exit(1)


def main():
    parser = argparse.ArgumentParser(
        description="Soleprint Build Tool",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )

    subparsers = parser.add_subparsers(dest="command", required=True)

    # dev command
    dev_parser = subparsers.add_parser("dev", help="Build for development (copies)")
    dev_parser.add_argument(
        "--output",
        "-o",
        type=Path,
        default=SPR_ROOT / "gen",
        help="Output directory (default: gen/)",
    )
    dev_parser.add_argument(
        "--cfg",
        "-c",
        type=str,
        default=None,
        help="Room config to include (e.g., 'amar')",
    )

    # deploy command
    deploy_parser = subparsers.add_parser(
        "deploy", help="Build for deployment (copies)"
    )
    deploy_parser.add_argument(
        "--output",
        "-o",
        type=Path,
        required=True,
        help="Output directory for deployment package",
    )
    deploy_parser.add_argument(
        "--cfg",
        "-c",
        type=str,
        default=None,
        help="Room config to include (e.g., 'amar')",
    )

    # models command
    subparsers.add_parser("models", help="Only regenerate models")

    args = parser.parse_args()

    if args.command == "dev":
        build_dev(args.output.resolve(), args.cfg)
    elif args.command == "deploy":
        build_deploy(args.output.resolve(), args.cfg)
    elif args.command == "models":
        build_models()


if __name__ == "__main__":
    main()
