#! /usr/bin/env python3
import argparse
import sys
import logging
import shutil
import subprocess
from textwrap import dedent
from pathlib import Path

OP_S3_CREDENTIAL_URI = "op://ieer6s7pb2di3x7tvpjwj24kki/qw4hf73666z37ivpahc7tsmoyq"
S3_BUCKET_NAME = "yum-repo"
REPOSITORIES = ["stable", "development"]

logger = logging.getLogger(__name__)


def read_1password_credential(uri: str) -> str:
    process = subprocess.Popen(
        ["op", "read", uri],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )

    # If there is no stdout, the command failed
    if not process.stdout:
        logger.error("Failed to read 1Password item")
        raise KeyError("Failed to read 1Password item")

    # Read the password from stdout
    return process.stdout.read().decode("utf-8").strip()


def build_rclone_env_vars():
    return {
        "RCLONE_CONFIG_EWPYUMREPO_TYPE": "s3",
        "RCLONE_CONFIG_EWPYUMREPO_PROVIDER": "Cloudflare",
        "RCLONE_CONFIG_EWPYUMREPO_ACCESS_KEY_ID": read_1password_credential(
            f"{OP_S3_CREDENTIAL_URI}/S3 Access Key ID"
        ),
        "RCLONE_CONFIG_EWPYUMREPO_SECRET_ACCESS_KEY": read_1password_credential(
            f"{OP_S3_CREDENTIAL_URI}/S3 Secret Access Key"
        ),
        "RCLONE_CONFIG_EWPYUMREPO_ENDPOINT": read_1password_credential(
            f"{OP_S3_CREDENTIAL_URI}/hostname"
        ),
    }


def ensure_repo_synced(temp_path: Path, side: str):
    logger.info(f"Ensuring {side} repo copy is up-to-date")
    temp_path.mkdir(parents=True, exist_ok=True)

    # Build the command
    cmd = ["rclone", "sync"]

    # Set the correct source and destination
    if side == "local":
        cmd += ["ewpyumrepo:yum-repo", str(temp_path)]
    else:
        cmd += [str(temp_path), "ewpyumrepo:yum-repo"]

    # Add dummy config
    cmd += ["--config", "/dev/null"]

    # Display progress
    cmd += ["--progress"]

    # Sync the entire directory locally
    subprocess.run(
        cmd,
        check=True,
        env=build_rclone_env_vars(),
    )


def update_repo(repo_path: Path):
    logger.info("Updating the repo")
    subprocess.run(
        ["createrepo", "--update", repo_path],
        check=True,
    )


def cmd_sync(args: argparse.Namespace) -> int:
    logger.info("Beginning sync")
    root_path = args.temp_path / "ewp-yum-repo"
    ensure_repo_synced(root_path, "local")

    # Generate a repo file that clients can download to use this repo
    with open(root_path / "ewpratten.repo", "w") as f:
        f.write(
            dedent(
                f"""
                [ewpratten]
                name = Evan's Software Repository
                baseurl = https://yum.ewpratten.com/stable
                enabled = 1
                gpgcheck = 0
                
                [ewpratten-dev]
                name = Evan's Software Repository (Development Builds)
                baseurl = https://yum.ewpratten.com/development
                enabled = 0
                gpgcheck = 0                
                """
            )
        )

    # Find all repos
    for repo in REPOSITORIES:
        repo = root_path / repo
        repo.mkdir(parents=True, exist_ok=True)

        # Update
        update_repo(repo)

    # Sync the repos
    ensure_repo_synced(root_path, "remote")

    return 0


def cmd_add(args: argparse.Namespace) -> int:
    root_path = args.temp_path / "ewp-yum-repo"
    ensure_repo_synced(root_path, "local")

    # Check the RPM file
    if not args.package.exists():
        logger.error(f"Package file '{args.package}' does not exist")
        return 1
    if not args.package.is_file():
        logger.error(f"Package file '{args.package}' is not a file")
        return 1
    if not args.package.suffix == ".rpm":
        logger.error(f"Package file '{args.package}' is not an RPM file")
        return 1

    # Ensure the needed package path exists
    package_root = root_path / args.branch / "Packages"
    package_root.mkdir(parents=True, exist_ok=True)
    logger.info(f"Adding package to: {package_root}")

    # Copy the file to the local repo
    shutil.copy(args.package, package_root)

    # Update the repo
    update_repo(package_root.parent)

    # Sync the repo
    ensure_repo_synced(root_path, "remote")

    return 0


def main() -> int:
    # Handle program arguments
    ap = argparse.ArgumentParser(
        prog="ewp-yum-repo", description="Manage yum.ewpratten.com"
    )
    subparsers = ap.add_subparsers(dest="command")
    ap.add_argument(
        "--temp-path",
        help="Path to store temporary files",
        type=Path,
        default=Path("/tmp"),
    )
    ap.add_argument(
        "-v", "--verbose", help="Enable verbose logging", action="store_true"
    )

    # Sync command
    sync_cmd_parser = subparsers.add_parser("sync", help="Sync everything")

    # Add command
    add_cmd_parser = subparsers.add_parser("add", help="Add a new package")
    add_cmd_parser.add_argument("package", help="Path to RPM file", type=Path)
    add_cmd_parser.add_argument(
        "--branch", help="Branch to add to", choices=REPOSITORIES, default="development"
    )

    # Parse everything
    args = ap.parse_args()

    # Configure logging
    logging.basicConfig(
        level=logging.DEBUG if args.verbose else logging.INFO,
        format="%(levelname)s:	%(message)s",
    )

    # We require a few tools to be on this system
    required_tools = ["op", "rclone"]
    for tool in required_tools:
        if shutil.which(tool) is None:
            logger.error(f"Required tool '{tool}' not found in $PATH")
            return 1

    # Handle the subcommands
    if args.command == "sync":
        return cmd_sync(args)
    elif args.command == "add":
        return cmd_add(args)
    else:
        ap.print_help()

    return 0


if __name__ == "__main__":
    sys.exit(main())